PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/system/expressionengine/third_party/ce_img/libraries/Ce_image.php

https://bitbucket.org/studiobreakfast/sync
PHP | 5901 lines | 4299 code | 580 blank | 1022 comment | 856 complexity | 00e446de9ac0bcf9f8e5b5f706fc82f8 MD5 | raw file
  1. <?php
  2. error_reporting(E_ALL);
  3. ini_set('display_errors', '1');
  4. /**
  5. * CE Image: Powerful image manipulation made easy.
  6. * Last Updated: 19 June 2012
  7. *
  8. * License:
  9. * CE Image is licensed under the Commercial License Agreement found at http://www.causingeffect.com/software/expressionengine/ce-image/license-agreement
  10. * Here are a couple of specific points from the license to note again:
  11. * - One license grants the right to perform one installation of CE Image. Each additional installation of CE Image requires an additional purchased license.
  12. * - You may not reproduce, distribute, or transfer CE Image, or portions thereof, to any third party.
  13. * - You may not sell, rent, lease, assign, or sublet CE Image or portions thereof.
  14. * - You may not grant rights to any other person.
  15. * - You may not use CE Image in violation of any United States or international law or regulation.
  16. * - The only exceptions to the above four (4) points are any methods clearly designated as having an MIT-style license. Those portions of code specifically designated as having an MIT-style license, and only those portions, will remain bound to the terms of that license.
  17. * If you have any questions about the terms of the license, or would like to report abuse of its terms, please contact software@causingeffect.com.
  18. *
  19. * @package CE Image
  20. * @author Causing Effect, Aaron Waldon
  21. * @link http://www.causingeffect.com
  22. * @copyright 2012
  23. * @version 1.6.5
  24. * @license http://www.causingeffect.com/software/expressionengine/ce-image/license-agreement Causing Effect Commercial License Agreement
  25. */
  26. class Ce_image
  27. {
  28. private $valid_filters = array(
  29. //native IMG_FILTER_ filters
  30. 'brightness' => 'imagefilter',
  31. 'colorize' => 'imagefilter',
  32. 'contrast' => 'imagefilter',
  33. 'edgedetect' => 'imagefilter',
  34. 'emboss' => 'imagefilter',
  35. 'grayscale' => 'imagefilter',
  36. 'mean_removal' => 'imagefilter',
  37. 'negate' => 'imagefilter',
  38. 'pixelate' => 'imagefilter',
  39. 'smooth' => 'imagefilter',
  40. //custom filters
  41. '_pixelate' => array( 'self', 'pixelate' ),
  42. 'gaussian_blur' => array( 'self', 'gaussian_blur' ),
  43. 'selective_blur' => array( 'self', 'selective_blur' ),
  44. 'auto_sharpen' => array( 'self', 'auto_sharpen' ),
  45. 'cemboss' => array( 'self', 'emboss_color' ),
  46. 'dot' => array( 'self', 'dot' ),
  47. 'noise' => array( 'self', 'noise' ),
  48. 'opacity' => array( 'self', 'opacity' ),
  49. 'replace_colors' => array( 'self', 'replace_colors' ),
  50. 'scatter' => array( 'self', 'scatter' ),
  51. 'sepia' => array( 'self', 'sepia' ),
  52. 'sharpen' => array( 'self', 'unsharp_mask' ),
  53. 'sobel_edgify' => array( 'self', 'prep_edgify' ) //calls class method before calling filter
  54. );
  55. private $factory_defaults = array(
  56. 'allow_overwrite_original' => false,
  57. 'allow_scale_larger' => false,
  58. 'auto_cache' => false,
  59. 'base' => '',
  60. 'bg_color' => false,
  61. 'bg_color_default' => 'ffffff',
  62. 'border' => false,
  63. 'cache_dir' => '/images/made/',
  64. 'crop' => false,
  65. 'current_domain' => '',
  66. 'dir_permissions' => 0775,
  67. 'disable_xss_check' => false,
  68. 'fallback_src' => '',
  69. 'filename' => '',
  70. 'filename_prefix' => '',
  71. 'filename_suffix' => '',
  72. 'filters' => array(),
  73. 'flip' => false,
  74. 'force_remote' => false,
  75. 'hash_filename' => false,
  76. 'height' => 0,
  77. 'hide_relative_path' => false,
  78. 'image_permissions' => 0644,
  79. 'made_regex' => '',
  80. 'max_height' => 0,
  81. 'max_width' => 0,
  82. 'memory_limit' => 64,
  83. 'min_height' => 0,
  84. 'min_width' => 0,
  85. 'overwrite_cache' => false,
  86. 'quality' => 100,
  87. 'reflection' => false,
  88. 'remote_cache_time' => 1440,
  89. 'remote_dir' => '/images/remote/',
  90. 'rotate' => 0,
  91. 'rounded_corners' => false,
  92. 'save_type' => false,
  93. 'src_regex' => '',
  94. 'text' => false,
  95. 'unique' => 'filename',
  96. 'watermark' => false,
  97. 'width' => 0
  98. );
  99. //---------- don't change anything below here ----------
  100. private $is_open = false; //whether or not an image is actually open
  101. private $is_open_ready = false; //whether or not the image is ready to be opened
  102. private $is_data_ready = false; //whether or not the image data is ready
  103. private $image_data = array( 'width' => '', 'height' => '', 'type' => '', 'ext' => '' );
  104. private $image_data_orig = array( 'width' => '', 'height' => '', 'type' => '', 'ext' => '', 'src' => '' );
  105. private $image_types = array( 'png', 'gif', 'jpg', 'jpeg' );
  106. private $is_remote = false;
  107. private $remote_src = '';
  108. private $x_offset = 0;
  109. private $y_offset = 0;
  110. private $src_x_offset = 0;
  111. private $src_y_offset = 0;
  112. private $width_desired = 0;
  113. private $height_desired = 0;
  114. private $x_options = array('left', 'right', 'center');
  115. private $y_options = array('top', 'bottom', 'center');
  116. private $same_as_source = false;
  117. private $debug_messages = array();
  118. private $is_transparent = false;
  119. private $defaults = array();
  120. //watermark
  121. private $filetime_wm = 0;
  122. private $wm_image_data = array( 'width' => '', 'height' => '', 'type' => '', 'src' => '' );
  123. private $wm_array;
  124. //rounded corners
  125. private $corner_options = array('all', 'tl', 'tr', 'bl', 'br');
  126. private $corners = false;
  127. private $corners_array;
  128. //reflection
  129. private $reflection = false;
  130. private $reflection_array = array();
  131. //flag if actual image dimensions needs to be checked
  132. private $check_size = false;
  133. //flip
  134. private $flip_options = array('h', 'v');
  135. //disable XSS check
  136. private $disable_xss_check = false;
  137. //For dev testing with versions of PHP before 5.2.0, windows has to swap / with \
  138. private $windows_dev = false;
  139. private $final_action = 'none';
  140. /**
  141. * Initiates the class and optionally accepts an array of default settings. Also determines whether or not CodeIgniter is installed and available to use.
  142. *
  143. * @param array $default_settings The default settings.
  144. */
  145. function __construct( $default_settings = array() )
  146. {
  147. //set the factory defaults
  148. $this->defaults = $this->factory_defaults;
  149. //merge the params with the defaults
  150. $this->set_default_settings( $default_settings );
  151. $this->EE =& get_instance();
  152. }
  153. // ---------------------------------------------------------------------------- SETTINGS ----------------------------------------------------------------------------
  154. /**
  155. * Accepts settings to integrate with the default settings.
  156. *
  157. * @param array $default_settings The default settings that will override the previously set default settings.
  158. * @return void
  159. */
  160. public function set_default_settings( $default_settings = array() )
  161. {
  162. $defaults = $this->defaults;
  163. //override the defaults
  164. if ( count( $default_settings ) > 0 )
  165. {
  166. foreach ( $default_settings as $key => $value )
  167. {
  168. if ( ! in_array( $key, $defaults ) )
  169. {
  170. unset( $default_settings[$key] );
  171. }
  172. }
  173. $defaults = array_merge( $defaults, $default_settings );
  174. }
  175. //reset the defaults array
  176. $this->defaults = $defaults;
  177. unset( $defaults );
  178. //now that our default array is created, apply them as class properties
  179. $this->reset_to_default_settings();
  180. }
  181. /**
  182. * Accepts settings to temporarily override the default settings. The settings will not be persistent when subsequent images are opened/made.
  183. *
  184. * @param array $temp_settings The temporary settings that will override any previously set default settings.
  185. * @return void
  186. */
  187. private function set_temp_settings( $temp_settings = array() )
  188. {
  189. //reset class properties
  190. $this->check_size = false;
  191. $this->disable_xss_check = false;
  192. $this->filetime_wm = 0;
  193. $this->height_desired = 0;
  194. $this->is_remote = false;
  195. $this->remote_src = '';
  196. $this->src_x_offset = 0;
  197. $this->src_y_offset = 0;
  198. $this->width_desired = 0;
  199. $this->wm_array = array();
  200. $this->x_offset = 0;
  201. $this->y_offset = 0;
  202. $this->same_as_source = false;
  203. //reset the defaults
  204. $this->reset_to_default_settings();
  205. //set the temp settings
  206. if ( count( $temp_settings ) > 0 )
  207. {
  208. foreach ($temp_settings as $key => $val)
  209. {
  210. if ( in_array( $key, $this->factory_defaults ) )
  211. {
  212. $this->$key = $val;
  213. }
  214. }
  215. }
  216. }
  217. /**
  218. * Resets the class properties to match the default array
  219. *
  220. * @return void
  221. */
  222. private function reset_to_default_settings()
  223. {
  224. $defaults = $this->defaults;
  225. //set the class properties to the defaults array
  226. if ( count( $defaults ) > 0 )
  227. {
  228. foreach ($defaults as $key => $val)
  229. {
  230. $this->$key = $val;
  231. }
  232. }
  233. }
  234. /**
  235. * Resets the settings back to the 'factory defaults'
  236. *
  237. * @return void
  238. */
  239. public function reset_to_factory_settings()
  240. {
  241. $this->defaults = $this->factory_defaults;
  242. $this->reset_to_default_settings();
  243. }
  244. /**
  245. * Retrieves all of the current settings.
  246. *
  247. * @return array
  248. */
  249. private function get_current_settings()
  250. {
  251. $settings = array();
  252. foreach ( $this->factory_defaults as $name => $setting )
  253. {
  254. $settings[$name] = $this->$name;
  255. }
  256. return $settings;
  257. }
  258. /**
  259. * Removes double slashes, except when they are preceded by ':', so that 'http://', etc are preserved.
  260. *
  261. * @param string $str The string from which to remove the double slashes.
  262. * @return string The string with double slashes removed.
  263. */
  264. private function remove_duplicate_slashes( $str )
  265. {
  266. return preg_replace( '#(?<!:)//+#', '/', $str );
  267. }
  268. // ---------------------------------------------------------------------------- OPEN ----------------------------------------------------------------------------
  269. /**
  270. * Opens an image. Call this function if you would like to get data from an image without manipulating it. This function is also called by the 'make' method.
  271. *
  272. * @param string|resource $src The image source. Can be a relative path to document root, a full server (absolute) path, a URL (remote or local), an HTML snippet, or a GD2 image resource.
  273. * @param array $temp_settings The settings that will temporary override the previously set default settings, for this image only.
  274. * @return bool Will return true on success and false on failure.
  275. */
  276. public function open( $src = '', $temp_settings = array() )
  277. {
  278. $is_string = ! is_resource( $src );
  279. $this->set_temp_settings( $temp_settings );
  280. $this->is_data_ready = false;
  281. $this->is_open_ready = false;
  282. $this->is_open = false;
  283. //base path
  284. $base = ( $this->base != '') ? $this->base : $_SERVER['DOCUMENT_ROOT'];
  285. $base = str_replace('\\', '/', $this->EE->security->xss_clean( $base ) );
  286. $this->base = $this->remove_duplicate_slashes( $base . '/' );
  287. $this->debug_messages[] = "Base path: '$this->base'";
  288. if ( $is_string ) //string src
  289. {
  290. $this->src = $src;
  291. //check to make sure the source is not blank
  292. if ( $this->src == '' && $this->fallback_src == '' )
  293. {
  294. $this->debug_messages[] = 'Source and fallback source cannot both be blank.';
  295. return false;
  296. }
  297. $this->src = str_replace('\\', '/', $this->src);
  298. //get the source from the first image tag, if one exists
  299. if ( preg_match('#<img.+src="(.*)".*>#Uims', $this->src, $matches) )
  300. {
  301. $this->src = $matches[1];
  302. }
  303. $this->src = $this->EE->security->xss_clean( $this->src );
  304. $this->fallback_src = str_replace('\\', '/', $this->fallback_src);
  305. $this->fallback_src = $this->EE->security->xss_clean( $this->fallback_src );
  306. //source and fallback source
  307. $this->debug_messages[] = "Source image: '$this->src', Fallback image: '$this->fallback_src'";
  308. if ( $this->src == '' || $this->check_src_image() == false )
  309. {
  310. //source is not readable or does not exist, write debug message and check fallback source
  311. $this->debug_messages[] = "Source image is not readable or does not exist: '$this->src'.";
  312. $this->src = $this->fallback_src;
  313. if ( $this->src == '' || $this->check_src_image() == false )
  314. {
  315. //fallback source is not readable, not much more to do...
  316. $this->debug_messages[] = "Fallback source image is not readable or does not exist: '$this->src'.";
  317. return false;
  318. }
  319. }
  320. //get the path info
  321. $info = pathinfo( $this->src );
  322. //get the relative path
  323. if ( $this->is_above_root ) //normal
  324. {
  325. $this->relative = ( $this->hide_relative_path ) ? '' : preg_replace( '@' . preg_quote( $this->base, '@' ) . '@', '', '/' . $info['dirname'] . '/', 1 );
  326. }
  327. else //we don't want to reveal server info below web root
  328. {
  329. $this->relative = '';
  330. if ( ! $this->hide_relative_path && ! empty( $info['dirname'] ) )
  331. {
  332. $this->relative = substr( md5( $info['dirname'] ), 0, 16 ) . '/';
  333. }
  334. }
  335. }
  336. else //resource
  337. {
  338. $this->src = '';
  339. $this->handle = $src;
  340. $this->is_above_root = true;
  341. $this->relative = '';
  342. }
  343. //auto cache directory
  344. if ( $this->auto_cache != '' && $this->is_above_root )
  345. {
  346. $this->auto_cache = $this->EE->security->xss_clean( $this->remove_duplicate_slashes( '/' . $this->auto_cache . '/' ) );
  347. $this->cache_full = $this->remove_duplicate_slashes( $this->base . $this->relative . $this->auto_cache );
  348. }
  349. else
  350. {
  351. //cache paths
  352. $this->cache_dir = $this->remove_duplicate_slashes( '/' . $this->cache_dir . '/' );
  353. $this->cache_full = $this->remove_duplicate_slashes( $this->base . $this->cache_dir . $this->relative );
  354. }
  355. if ( $this->windows_dev )
  356. {
  357. $this->cache_full = str_replace( '/', '\\', $this->cache_full);
  358. }
  359. if ( $is_string ) //string src
  360. {
  361. //get the image data
  362. $status = $this->get_image_data();
  363. if ( $status === 'try again' )
  364. {
  365. return $this->open( $this->fallback_src );
  366. }
  367. else if ($status === false)
  368. {
  369. return false;
  370. }
  371. //original extension (use this if possible to preserve casing)
  372. if ( isset( $info['extension'] ) )
  373. {
  374. $this->image_data_orig['ext'] = $info['extension'];
  375. }
  376. else
  377. {
  378. $this->image_data_orig['ext'] = '';
  379. }
  380. //get original filename
  381. if ( isset( $info['filename'] ) ) //>= PHP 5.2.0
  382. {
  383. $filename = $info['filename'];
  384. }
  385. else //< PHP 5.2.0
  386. {
  387. $filename = substr_replace( $info['basename'], '', strrpos( $info['basename'], '.' . $this->image_data_orig['ext'] ), strlen( '.' . $this->image_data_orig['ext'] ) );
  388. }
  389. //store original filename
  390. $this->filename_orig = $this->EE->security->sanitize_filename( $filename );
  391. $this->filename_final = $this->filename_orig;
  392. }
  393. else //resource
  394. {
  395. $filename = 'temp';
  396. $width = round( imagesx( $this->handle ) );
  397. $height = round( imagesy( $this->handle ) );
  398. $this->image_data = array( 'width' => $width, 'height' => $height, 'type' => '', 'ext' => '' );
  399. $this->image_data_orig = array( 'width' => $width, 'height' => $height, 'type' => '', 'ext' => '', 'src' => '' );
  400. $this->filename_orig = '';
  401. $this->filename_final = '';
  402. $this->is_open = true;
  403. }
  404. //---------- filename ----------
  405. $temp = trim( $this->filename );
  406. $filename = ( $temp === '' ) ? $filename : $temp;
  407. $this->filename = $this->EE->security->sanitize_filename( $filename );
  408. $this->is_open_ready = true;
  409. $this->is_data_ready = true;
  410. return true;
  411. }
  412. /**
  413. * Retrieves the image data for the current src image.
  414. *
  415. * @return bool Will return true on success and false on failure.
  416. */
  417. private function get_image_data()
  418. {
  419. //get the image info
  420. $data = @getimagesize( $this->src );
  421. $success = true;
  422. if ( ! $data )
  423. {
  424. $success = false;
  425. }
  426. else
  427. {
  428. $ext = '';
  429. switch ( $data[2] )
  430. {
  431. case IMAGETYPE_GIF:
  432. $ext = 'gif';
  433. break;
  434. case IMAGETYPE_PNG:
  435. $ext = 'png';
  436. break;
  437. case IMAGETYPE_JPEG:
  438. $ext = 'jpg';
  439. break;
  440. default:
  441. $success = false;
  442. }
  443. }
  444. if ( ! $success )
  445. {
  446. $this->debug_messages[] = 'Unknown image format.';
  447. //there is a small chance the image was an error page, try and use the fallback source
  448. if ( $this->src != $this->fallback_src && $this->fallback_src != '' ) //retry with the fallback as the source
  449. {
  450. $this->debug_messages[] = 'Reprocessing using the fallback image.';
  451. return 'try again'; //try again
  452. }
  453. return false;
  454. }
  455. //$this->image_data_orig['type'] = $data[2];
  456. $this->image_data = array( 'width' => $data[0], 'height' => $data[1], 'ext' => $ext );
  457. $this->image_data_orig = array( 'width' => $data[0], 'height' => $data[1], 'ext' => $ext, 'type' => $data[2], 'src' => $this->src );
  458. return true;
  459. }
  460. /**
  461. * Opens the actual image resource.
  462. * @param bool $original
  463. * @return bool Returns true on success and false on failure.
  464. */
  465. private function open_real( $original = false )
  466. {
  467. if ( ! $this->is_open_ready )
  468. {
  469. //return false;
  470. $this->open( $this->src );
  471. }
  472. if ( $this->is_open )
  473. {
  474. return true;
  475. }
  476. //set the memory limit
  477. $this->set_memory_limit();
  478. if ( $this->src != '' ) //do not try to open if the src is '', because it's a resource
  479. {
  480. $which = ( $original ) ? 'image_data_orig' : 'image_data';
  481. if ( isset( $this->{$which}['type'] ) && $this->{$which}['type'] != '' )
  482. {
  483. switch ( $this->{$which}['type'] )
  484. {
  485. case IMAGETYPE_GIF:
  486. $type = 'gif';
  487. break;
  488. case IMAGETYPE_PNG:
  489. $type = 'png';
  490. break;
  491. case IMAGETYPE_JPEG:
  492. $type = 'jpg';
  493. break;
  494. default:
  495. $type = '';
  496. }
  497. }
  498. else if (in_array(strtolower($this->{$which}['ext']), $this->image_types))
  499. {
  500. $type = strtolower($this->{$which}['ext']);
  501. }
  502. //open the file and create main image handle
  503. switch ( $type )
  504. {
  505. case 'gif':
  506. $success = @imagecreatefromgif( $this->src );
  507. break;
  508. case 'png':
  509. $success = @imagecreatefrompng( $this->src );
  510. break;
  511. case 'jpg':
  512. case 'jpeg':
  513. $success = @imagecreatefromjpeg( $this->src );
  514. break;
  515. default:
  516. $this->debug_messages[] = 'Unknown image format for image creation.';
  517. $success = false;
  518. }
  519. if ( $success === false )
  520. {
  521. $this->debug_messages[] = "A problem occurred trying to open the image '$this->src'.";
  522. return false;
  523. }
  524. else
  525. {
  526. $this->handle = $success;
  527. }
  528. }
  529. $this->is_open = true;
  530. $this->debug_messages[] = "Image opened '$this->src'.";
  531. return true;
  532. }
  533. /**
  534. * Attempts to determine the server path from the image src parameter. Will also download a remote image if applicable and return its server path.
  535. *
  536. * @return bool Returns true if the image is found successfully and false otherwise.
  537. */
  538. private function check_src_image()
  539. {
  540. //perform any advanced preg_replaces if they exist
  541. $replacements = $this->src_regex;
  542. if ( $replacements != false && is_array( $replacements ) && count( $replacements ) > 0 )
  543. {
  544. $find = array();
  545. $replace = array();
  546. foreach( $replacements as $f => $r )
  547. {
  548. $find[] = '@' . $f . '@';
  549. $replace[] = $r;
  550. }
  551. $this->src = preg_replace( $find, $replace, $this->src );
  552. //show the new source
  553. $this->debug_messages[] = "Regexed source: '$this->src'";
  554. }
  555. //adjust for protocol-relative URLs
  556. if ( substr( $this->src, 0, 3 ) == '://' )
  557. {
  558. if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' )
  559. {
  560. $this->src = 'https' . $this->src;
  561. }
  562. else
  563. {
  564. $this->src = 'http' . $this->src;
  565. }
  566. }
  567. //check if a URL
  568. if ( substr( $this->src, 0, 4 ) == 'http' )
  569. {
  570. $info = parse_url( $this->src );
  571. if ( $info === false )
  572. {
  573. $this->debug_messages[] = "The url '{$this->src}' could not be parsed. Is your URL malformed?";
  574. return false;
  575. }
  576. $current_domain_override = $this->current_domain;
  577. if ( $current_domain_override == '' ) //no domain specified, try to figure it out
  578. {
  579. $current_domain_override = $this->EE->security->xss_clean( preg_replace( '@' . preg_quote( 'www.', '@' ) . '@', '', $_SERVER['SERVER_NAME'], 1 ) );
  580. }
  581. else //use the current domain value as set in plugin or config
  582. {
  583. $temp = parse_url( $current_domain_override );
  584. if ( $temp === false )
  585. {
  586. $this->debug_messages[] = "The url '{$current_domain_override}' could not be parsed. Is it malformed?";
  587. return false;
  588. }
  589. if ( ! isset( $temp['host'] ) ) //verify that the host key is set
  590. {
  591. $this->debug_messages[] = "The value specified for the 'current_domain' is invalid.";
  592. return false;
  593. }
  594. $current_domain_override = preg_replace( '@' . preg_quote( 'www.', '@') . '@', '', $temp['host'], 1 );
  595. }
  596. $path = isset( $info['path'] ) ? $info['path'] : '';
  597. if ( $this->force_remote || preg_replace( '@' . preg_quote( 'www.', '@') . '@', '', $info['host'], 1 ) != $current_domain_override ) //remote
  598. {
  599. $this->is_remote = true;
  600. $this->remote_src = $this->src;
  601. //get remote_dir param
  602. $this->remote_dir = $this->remove_duplicate_slashes( '/' . $this->remote_dir . '/' );
  603. $temp = $this->base . '/' . $this->remote_dir . $info['scheme'] . '_' . $info['host'] . $path;
  604. //create and sanitize the file path
  605. $temp = $this->EE->security->xss_clean( $this->remove_duplicate_slashes( $temp ) );
  606. $path_info = pathinfo( $temp );
  607. //make sure the query string gets worked in if applicable
  608. if ( isset( $info['query'] ) )
  609. {
  610. $path_info['dirname'] = $this->remove_duplicate_slashes( $path_info['dirname'] . '/' . md5( $info['query'] ) );
  611. }
  612. $remote_cache = $path_info['dirname'];
  613. $remote_filename = $this->EE->security->sanitize_filename( $path_info['basename'] );
  614. $file = $remote_cache . '/' . $remote_filename;
  615. $file_exists = @file_exists( $file );
  616. if ( $file_exists )
  617. {
  618. //determine the course of action for refreshing the remote image depending on the settings
  619. $file_m_time = @filemtime( $file ); //filetime of cached image
  620. if ( $this->remote_cache_time != -1 ) //don't worry about re-downloading the remote image if remote_cache_time is -1
  621. {
  622. //check the timestamp of the image
  623. $remote_timestamp = 0;
  624. //see if the user has curl so we can check the timestamp
  625. if ( $this->remote_cache_time == 0 && function_exists( 'curl_init' ) ) //check if the remote image is newer than the downloaded version if remote_cache_time is 0
  626. {
  627. $this->debug_messages[] = "Checking whether the remote image '{$this->remote_src}' is newer than the cached version via cURL.";
  628. $curl = curl_init( $this->remote_src );
  629. curl_setopt($curl, CURLOPT_NOBODY, true); //only fetch headers
  630. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //no output
  631. curl_setopt($curl, CURLOPT_FILETIME, true); //attempt to get date modified
  632. //follow location will throw an error if safe mode or an open basedir restrictions is enabled
  633. if ( ! ini_get('open_basedir') && ! ini_get('safe_mode') )
  634. {
  635. curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
  636. }
  637. if ( curl_exec($curl) !== false)
  638. {
  639. $remote_timestamp = curl_getinfo($curl, CURLINFO_FILETIME);
  640. }
  641. }
  642. $delete_cache = false;
  643. if ( $remote_timestamp != 0 && $remote_timestamp > $file_m_time ) //the remote image is newer
  644. {
  645. $this->debug_messages[] = "The remote image '{$this->remote_src}' is newer than the cached version.";
  646. $delete_cache = true;
  647. }
  648. else if (time() - $file_m_time > $this->remote_cache_time * 60) //the remote file has expired
  649. {
  650. $this->debug_messages[] = "The local cache of the remote image has expired.";
  651. $delete_cache = true;
  652. }
  653. //delete the current remote cache file
  654. if ( $delete_cache )
  655. {
  656. @unlink( $file );
  657. //recheck if the file exists
  658. $file_exists = @file_exists( $file );
  659. }
  660. }
  661. }
  662. //if the image is not on the server, download it
  663. if ( ! $file_exists )
  664. {
  665. if ( $this->windows_dev )
  666. {
  667. $remote_cache = str_replace('/', '\\', $remote_cache);
  668. }
  669. if ( $this->save_remote_image( $remote_cache, $remote_filename ) === false )
  670. {
  671. return false;
  672. }
  673. }
  674. //change the source from the remote image to the downloaded image
  675. $this->src = $this->remove_duplicate_slashes( $remote_cache . '/' . $remote_filename );
  676. }
  677. else //local
  678. {
  679. $this->src = $path;
  680. }
  681. }
  682. //---------- figure out source path ----------
  683. $this->is_above_root = true;
  684. $temp = $this->remove_duplicate_slashes( $this->base . $this->src );
  685. if ( @is_readable( $temp ) === true ) //relative path
  686. {
  687. //create full path for source
  688. $this->src = $this->remove_duplicate_slashes( realpath($temp) );
  689. $this->src = str_replace('\\', '/', $this->src);
  690. }
  691. //see if path below web root
  692. if ( strpos($this->src, $this->base) === false )
  693. {
  694. $this->is_above_root = false;
  695. }
  696. //make sure the image is readable and is not a directory
  697. if ( @is_dir( $this->src ) || @is_readable( $this->src ) !== true )
  698. {
  699. return false;
  700. }
  701. return true;
  702. }
  703. /**
  704. * Saves a remote image.
  705. *
  706. * @param string $remote_cache The server path to the remote images' cache folder.
  707. * @param string $remote_filename The URL of the remote image.
  708. * @return bool Returns true if the image is saved successfully and false otherwise.
  709. */
  710. private function save_remote_image( $remote_cache, $remote_filename )
  711. {
  712. $this->set_memory_limit();
  713. @ini_set('default_socket_timeout', 30);
  714. @ini_set('allow_url_fopen', true);
  715. @ini_set('user_agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:5.0) Gecko/20100101');
  716. $url = str_replace( ' ', '%20', $this->src );
  717. if ( function_exists( 'curl_init' ) ) //try to get the image using cURL
  718. {
  719. $curl = curl_init();
  720. curl_setopt($curl, CURLOPT_URL, $url ); //set the URL
  721. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //no output
  722. curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, false); //no timeout
  723. //follow location will throw an error if safe mode or an open basedir restrictions is enabled
  724. if ( ! ini_get('open_basedir') && ! ini_get('safe_mode') )
  725. {
  726. curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
  727. }
  728. $remote_image = curl_exec($curl);
  729. if ( empty( $remote_image ) ) //cURL failed
  730. {
  731. $this->debug_messages[] = 'Could not get remote image using cURL.';
  732. return false;
  733. }
  734. }
  735. else //try to get the image using file_get_contents
  736. {
  737. $remote_image = @file_get_contents( $url );
  738. if ( empty( $remote_image ) ) //file_get_contents failed and cURL is not enabled, not much more we can do...
  739. {
  740. $this->debug_messages[] = 'Could not get remote image using file_get_contents.';
  741. return false;
  742. }
  743. }
  744. if ( $this->disable_xss_check || $this->EE->security->xss_clean( $remote_image, true ) === true )
  745. {
  746. //check if the directory exists
  747. if ( ! @is_dir( $remote_cache ) )
  748. {
  749. //try to make the directory
  750. if ( ! @mkdir( $remote_cache . '/', $this->dir_permissions, true ) )
  751. {
  752. $this->debug_messages[] = "Could not create the cache directory '$remote_cache/' for remote images.";
  753. return false;
  754. }
  755. }
  756. //ensure the directory is writable
  757. if ( ! @is_writable( $remote_cache ) )
  758. {
  759. $this->debug_messages[] = "Cache directory for remote images '$remote_cache' is not writable.";
  760. return false;
  761. }
  762. if ( file_put_contents( $remote_cache . '/' . $remote_filename, $remote_image) === false )
  763. {
  764. $this->debug_messages[] = "Could not write the remote image '{$remote_cache}/{$remote_filename}'.";
  765. return false;
  766. }
  767. }
  768. else //file failed the XSS test
  769. {
  770. $this->debug_messages[] = 'Remote image failed XSS test.';
  771. return false;
  772. }
  773. //the image should have been saved successfully, let's attempt to update permissions
  774. if ( ! @chmod( $remote_cache . '/' . $remote_filename, $this->image_permissions ) )
  775. {
  776. $this->debug_messages[] = "File permissions for '{$remote_cache}/{$remote_filename}' could not be changed to '$this->image_permissions'.";
  777. }
  778. $remote_image = null;
  779. $this->debug_messages[] = "The remote image '{$url}' was downloaded successfully.";
  780. return true;
  781. }
  782. // ---------------------------------------------------------------------------- RETURN FUNCTIONS ----------------------------------------------------------------------------
  783. /**
  784. * The currently open image resource.
  785. *
  786. * @return resource|bool The resource on success and false on failure.
  787. */
  788. public function get_resource()
  789. {
  790. if ( $this->open_real() == false )
  791. {
  792. $this->debug_messages[] = 'A resource could not be returned, because an image is not open.';
  793. return false;
  794. }
  795. if ( isset( $this->handle ) && is_resource($this->handle) )
  796. {
  797. return $this->handle;
  798. }
  799. else
  800. {
  801. $this->debug_messages[] = 'A resource could not be returned, because a valid resource was not found.';
  802. return false;
  803. }
  804. }
  805. /**
  806. * Returns a clone of the currently open image resource.
  807. *
  808. * @return resource|bool The resource clone on success and false on failure.
  809. */
  810. public function clone_resource()
  811. {
  812. if ( $this->open_real() == false )
  813. {
  814. $this->debug_messages[] = 'A resource could not be cloned, because an image is not open.';
  815. return false;
  816. }
  817. if ( isset( $this->handle ) && is_resource( $this->handle ) )
  818. {
  819. $width = round( imagesx( $this->handle ) );
  820. $height = round( imagesy( $this->handle ) );
  821. $clone = imagecreatetruecolor( $width, $height );
  822. if ( @imagecopy( $clone, $this->handle, 0, 0, 0, 0, $width, $height ) )
  823. {
  824. return $clone;
  825. }
  826. else
  827. {
  828. $this->debug_messages[] = 'There was an error cloning the image.';
  829. return false;
  830. }
  831. }
  832. else
  833. {
  834. $this->debug_messages[] = 'There image could not be cloned, because a valid resource was not found.';
  835. return false;
  836. }
  837. }
  838. /**
  839. * Returns the type of action that was taken. Can be one of the following:
  840. * none - No action was taken.
  841. * saved - The image was successfully saved.
  842. * cached - A cached image was found and used.
  843. *
  844. * @return string
  845. */
  846. public function get_final_action()
  847. {
  848. return $this->final_action;
  849. }
  850. /**
  851. * The height of the current image.
  852. *
  853. * @return int The current image's height.
  854. */
  855. public function get_height()
  856. {
  857. return $this->image_data['height'];
  858. }
  859. /**
  860. * The height of the original image.
  861. *
  862. * @return int The original image's height
  863. */
  864. public function get_original_height()
  865. {
  866. return $this->image_data_orig['height'];
  867. }
  868. /**
  869. * The width of the current image.
  870. *
  871. * @return int The current image's width.
  872. */
  873. public function get_width()
  874. {
  875. return $this->image_data['width'];
  876. }
  877. /**
  878. * The width of the original image.
  879. *
  880. * @return int The original image's width.
  881. */
  882. public function get_original_width()
  883. {
  884. return $this->image_data_orig['width'];
  885. }
  886. /**
  887. * The extension of the current image.
  888. *
  889. * @return string The current image's extension.
  890. */
  891. public function get_extension()
  892. {
  893. return ( $this->get_server_path() != '' ) ? $this->image_data['ext'] : '';
  894. }
  895. /**
  896. * The extension of the original image.
  897. *
  898. * @return string The original image's extension.
  899. */
  900. public function get_original_extension()
  901. {
  902. return $this->image_data_orig['ext'];
  903. }
  904. /**
  905. * The relative path (from document root) of the current image.
  906. *
  907. * @return string The current image's relative path.
  908. */
  909. public function get_relative_path()
  910. {
  911. $path = preg_replace( '@' . preg_quote( $this->base, '@' ) . '@', '', '/' . $this->src, 1 );
  912. //perform any advanced preg_replaces if they exist
  913. $replacements = $this->made_regex;
  914. if ( $replacements != false && is_array( $replacements ) && count( $replacements ) > 0 )
  915. {
  916. $find = array();
  917. $replace = array();
  918. foreach( $replacements as $f => $r )
  919. {
  920. $find[] = '#' . $f . '#';
  921. $replace[] = $r;
  922. }
  923. $path = preg_replace( $find, $replace, $path );
  924. //show the new made path
  925. $this->debug_messages[] = "Regexed made: '$path'.";
  926. }
  927. return $path;
  928. }
  929. /**
  930. * The relative path (from document root) of the original image.
  931. *
  932. * @return string The original image's relative path.
  933. */
  934. public function get_original_relative_path()
  935. {
  936. $path = preg_replace( '@' . preg_quote( $this->base, '@' ) . '@', '', '/' . $this->image_data_orig['src'], 1 );
  937. return $path;
  938. }
  939. /**
  940. * The file type of the current image. Will return 'jpg', 'png', or 'gif'.
  941. *
  942. * @return string
  943. */
  944. public function get_type()
  945. {
  946. $type = strtolower( $this->image_data['ext'] );
  947. if ( $type == 'jpeg' )
  948. {
  949. $type = 'jpg';
  950. }
  951. return $type;
  952. }
  953. /**
  954. * The file type of the original image. Will return 'jpg', 'png', or 'gif'.
  955. *
  956. * @return string
  957. */
  958. public function get_type_orig()
  959. {
  960. switch ( $this->image_data_orig['type'] )
  961. {
  962. case 1;
  963. return 'gif';
  964. break;
  965. case 2:
  966. return 'jpg';
  967. break;
  968. case 3:
  969. return 'png';
  970. break;
  971. }
  972. return 'unknown';
  973. }
  974. /**
  975. * The file name of the current image, without the file extension.
  976. *
  977. * @return string The current image's file name.
  978. */
  979. public function get_filename()
  980. {
  981. return ( $this->get_server_path() != '' ) ? $this->filename_final : '';
  982. }
  983. /**
  984. * The file name of the original image, without the file extension.
  985. *
  986. * @return string The original image's file name.
  987. */
  988. public function get_original_filename()
  989. {
  990. return $this->filename_orig;
  991. }
  992. /**
  993. * The server path of the current image.
  994. *
  995. * @return string The current image's server path.
  996. */
  997. public function get_server_path()
  998. {
  999. return $this->src;
  1000. }
  1001. /**
  1002. * The server path of the original image.
  1003. *
  1004. * @return string The original image's server path.
  1005. */
  1006. public function get_original_server_path()
  1007. {
  1008. return $this->image_data_orig['src'];
  1009. }
  1010. /**
  1011. * The array of debug messages.
  1012. *
  1013. * @return array The debug messages.
  1014. */
  1015. public function get_debug_messages()
  1016. {
  1017. return $this->debug_messages;
  1018. }
  1019. /**
  1020. * Returns the file size of the image.
  1021. *
  1022. * @param bool $bytes_only If the file size should be returned in bytes, or converted to a human readable format (default).
  1023. * @return int|string|bool Returns int if $bytes_only is true, string if $bytes_only is false, and false if an error.
  1024. */
  1025. public function get_filesize( $bytes_only = false )
  1026. {
  1027. if ( $this->src != '' && ( $this->is_data_ready || $this->is_open_ready ) )
  1028. {
  1029. $filesize = @filesize( $this->src );
  1030. if ( $filesize !== false)
  1031. {
  1032. return ( $bytes_only ) ? $filesize : Ce_image_tools::convert( $filesize );
  1033. }
  1034. else
  1035. {
  1036. $this->debug_messages[] = "The file size could not be found for '$this->src'.";
  1037. return false;
  1038. }
  1039. }
  1040. else
  1041. {
  1042. $this->debug_messages[] = 'The file size could not be retrieved, because an image is not open.';
  1043. return false;
  1044. }
  1045. }
  1046. /**
  1047. * Returns the file size of the original image.
  1048. *
  1049. * @param bool $bytes_only If the file size should be returned in bytes, or converted to a human readable format (default).
  1050. * @return int|string|bool Returns int if $bytes_only is true, string if $bytes_only is false, and false if an error.
  1051. */
  1052. public function get_original_filesize( $bytes_only = false )
  1053. {
  1054. if ( $this->get_original_server_path() != '' && ( $this->is_data_ready || $this->is_open_ready ) )
  1055. {
  1056. $filesize = @filesize( $this->image_data_orig['src'] );
  1057. if ( $filesize !== false)
  1058. {
  1059. return ( $bytes_only ) ? $filesize : Ce_image_tools::convert( $filesize );
  1060. }
  1061. else
  1062. {
  1063. $this->debug_messages[] = "The original file size could not be found for '{$this->image_data_orig['src']}'.";
  1064. return false;
  1065. }
  1066. }
  1067. else
  1068. {
  1069. $this->debug_messages[] = 'The original file size could not be retrieved, because an image is not open.';
  1070. return false;
  1071. }
  1072. }
  1073. /**
  1074. * Creates a tag for the current image. It will automatically add in the images width, height, and alt tag if they are not passed in as attributes.
  1075. *
  1076. * @param array $attributes The attributes to be used for the image.
  1077. * @param bool $closing_slash Whether or not to include a closing slash (defaults to true).
  1078. * @return string The HTML image tag.
  1079. */
  1080. public function create_tag( $attributes = array(), $closing_slash = true )
  1081. {
  1082. if ( $this->src == '' OR ! $this->is_data_ready )
  1083. {
  1084. $this->debug_messages[] = 'The image tag could not be created, because an image is not open.';
  1085. return false;
  1086. }
  1087. if ( ! array_key_exists( 'src', $attributes ) )
  1088. {
  1089. $attributes['src'] = $this->get_relative_path();
  1090. }
  1091. if ( ! array_key_exists( 'width', $attributes ) )
  1092. {
  1093. $attributes['width'] = $this->get_width();
  1094. }
  1095. if ( ! array_key_exists( 'height', $attributes ) )
  1096. {
  1097. $attributes['height'] = $this->get_height();
  1098. }
  1099. if ( ! array_key_exists( 'alt', $attributes ) )
  1100. {
  1101. $attributes['alt'] = '';
  1102. }
  1103. $attr = '';
  1104. foreach( $attributes as $attribute => $value )
  1105. {
  1106. $value = str_replace('"', '&quot;', $value );
  1107. $attr .= $attribute . '="' . $value . '" ';
  1108. }
  1109. $closing_slash = ($closing_slash) ? '/' : '';
  1110. return '<img ' . $attr . $closing_slash . '>';
  1111. }
  1112. /**
  1113. * Creates a tag for the original image. It will automatically add in the images width, height, and alt tag if they are not passed in as attributes.
  1114. *
  1115. * @param array $attributes The attributes to be used for the image.
  1116. * @param bool $closing_slash Whether or not to include a closing slash (defaults to true).
  1117. * @return string The HTML image tag.
  1118. */
  1119. public function create_original_tag( $attributes = array(), $closing_slash = true )
  1120. {
  1121. if ( $this->get_original_server_path() == '' OR ! $this->is_data_ready )
  1122. {
  1123. $this->debug_messages[] = 'The image tag could not be created, because an image is not open.';
  1124. return false;
  1125. }
  1126. if ( ! array_key_exists( 'src', $attributes ) )
  1127. {
  1128. $attributes['src'] = $this->get_original_relative_path();
  1129. }
  1130. if ( ! array_key_exists( 'width', $attributes ) )
  1131. {
  1132. $attributes['width'] = $this->get_original_width();
  1133. }
  1134. if ( ! array_key_exists( 'height', $attributes ) )
  1135. {
  1136. $attributes['height'] = $this->get_original_height();
  1137. }
  1138. if ( ! array_key_exists( 'alt', $attributes ) )
  1139. {
  1140. $attributes['alt'] = '';
  1141. }
  1142. $attr = '';
  1143. foreach( $attributes as $attribute => $value )
  1144. {
  1145. $value = str_replace('"', '&quot;', $value );
  1146. $attr .= $attribute . '="' . $value . '" ';
  1147. }
  1148. $closing_slash = ($closing_slash) ? '/' : '';
  1149. return '<img ' . $attr . $closing_slash . '>';
  1150. }
  1151. /**
  1152. * Creates ASCII art for an image. Each character represents one pixel from the image.
  1153. *
  1154. * @param bool $use_colors Whether or not to colorize the characters.
  1155. * @param array $ascii_characters The array of characters to be used for the ASCII art.
  1156. * @param bool $repeat Whether or not the characters should repeat in consecutive order (true) or be placed depending on the darkness of the pixel (false).
  1157. * @param bool $space_for_trans If the $repeat parameter is set to true, you can set this parameter to determine whether or not a space should be used for transparent pixels.
  1158. * @return string|bool The HTML for the ASCII art on success, false on failure.
  1159. */
  1160. public function get_ascii_art( $use_colors = true, $ascii_characters = array('#', '@', '%', '=', '+', '*', ':', '-', '.', '&nbsp;'), $repeat = false, $space_for_trans = false )
  1161. {
  1162. //check the settings
  1163. if ( ! is_bool( $use_colors) )
  1164. {
  1165. $use_colors = true;
  1166. }
  1167. //repeat characters?
  1168. if ( ! is_bool( $repeat) )
  1169. {
  1170. $repeat = false;
  1171. }
  1172. //use space character for transparent pixels?
  1173. if ( ! is_bool( $space_for_trans ) )
  1174. {
  1175. $space_for_trans = false;
  1176. }
  1177. if ( $this->open_real() == false )
  1178. {
  1179. $this->debug_messages[] = 'The ASCII art could not be generated, because an image is not open.';
  1180. return false;
  1181. }
  1182. return $this->create_ascii_art( $this->handle, $use_colors, $ascii_characters, $repeat, $space_for_trans );
  1183. }
  1184. /**
  1185. * Organizes the colors used in your image by frequency of occurrence, and groups them by a threshold.
  1186. *
  1187. * @param int $quantity The maximum number of colors (color groups) to return.
  1188. * @param int $threshold Value from 0 (very low grouping) to 100 (very high grouping).
  1189. * @return array|bool On success, returns an array of results with each result being an array with the following keys: 'color', 'color_count', 'color_percent'. On failure, returns false.
  1190. */
  1191. public function get_top_colors( $quantity = 5, $threshold = 33 )
  1192. {
  1193. if ( ! is_numeric( $quantity) )
  1194. {
  1195. $this->debug_messages[] = 'Invalid quantity of return colors for top colors.';
  1196. return false;
  1197. }
  1198. if ( ! is_numeric( $threshold ) || $threshold < 0 || $threshold > 100 )
  1199. {
  1200. $this->debug_messages[] = 'Invalid threshold for top colors. Must be between 0 and 100, inclusive.';
  1201. return false;
  1202. }
  1203. if ( $this->open_real() )
  1204. {
  1205. return $this->find_top_colors( $this->handle, $quantity, $threshold );
  1206. }
  1207. else
  1208. {
  1209. $this->debug_messages[] = 'The top colors could not be retrieved, because an image is not open.';
  1210. return false;
  1211. }
  1212. }
  1213. /**
  1214. * Gets the average color for the image.
  1215. *
  1216. * @return string The hexadecimal color value of the average color of the generated image.
  1217. */
  1218. public function get_average_color()
  1219. {
  1220. if ( $this->open_real() )
  1221. {
  1222. return $this->find_average_color( $this->handle );
  1223. }
  1224. else
  1225. {
  1226. $this->debug_messages[] = 'The average color could not be retrieved, because no image is open.';
  1227. return false;
  1228. }
  1229. }
  1230. // ---------------------------------------------------------------------------- CLOSE ----------------------------------------------------------------------------
  1231. /**
  1232. * Closes the image.
  1233. *
  1234. * @return void
  1235. */
  1236. public function close()
  1237. {
  1238. if ( $this->is_open )
  1239. {
  1240. $this->close_real();
  1241. }
  1242. $this->src = '';
  1243. $this->fallback_src = '';
  1244. $this->image_data = array( 'width' => '', 'height' => '', 'ext' => '');
  1245. $this->image_data_orig = array( 'width' => '', 'height' => '', 'type' => '', 'ext' => '', 'src' => '');
  1246. $this->wm_image_data = array( 'width' => '', 'height' => '', 'type' => '', 'src' => '' );
  1247. $this->debug_messages = array();
  1248. $this->is_data_ready = false;
  1249. }
  1250. /**
  1251. * Closes the open image resource(s).
  1252. *
  1253. * @return void
  1254. */
  1255. private function close_real()
  1256. {
  1257. if ( is_resource( $this->handle ) )
  1258. {
  1259. imagedestroy( $this->handle );
  1260. unset( $this->handle );
  1261. }
  1262. $this->is_open = false;
  1263. }
  1264. // ---------------------------------------------------------------------------- PREPARE SETTINGS ----------------------------------------------------------------------------
  1265. /**
  1266. * Cleans up some of the settings to ensure they are in the correct format for the class to use.
  1267. *
  1268. * @return bool true on success, false on failure
  1269. */
  1270. private function prep_settings()
  1271. {
  1272. //---------- get other params ----------
  1273. //allow_scale_larger parameter
  1274. if ( ! is_bool( $this->allow_scale_larger ) )
  1275. {
  1276. $this->allow_scale_larger = false;
  1277. }
  1278. //get bg_color param
  1279. $this->bg_color = Ce_image_tools::hex_cleanup( $this->bg_color );
  1280. //get bg_color_default param
  1281. $this->bg_color_default = Ce_image_tools::hex_cleanup( $this->bg_color_default );
  1282. if ( $this->bg_color_default == false )
  1283. {
  1284. $this->bg_color_default = 'ffffff';
  1285. }
  1286. //get filter param
  1287. $filter_array = $this->filters;
  1288. if ( ! is_array( $filter_array ) )
  1289. {
  1290. $filter_array = ( trim( $filter_array ) != '' ) ? array( $filter_array ) : array();
  1291. }
  1292. $filters = array();
  1293. $count = count( $filter_array );
  1294. if ( $count != 0 )
  1295. {
  1296. foreach( $filter_array as $filter )
  1297. {
  1298. //make filter an array if applicable
  1299. if ( ! is_array($filter) && trim( $filter ) != '' )
  1300. {
  1301. $filter = array( $filter );
  1302. }
  1303. //make sure the filter has a name
  1304. if ( ! isset( $filter['0'] ) || trim($filter['0']) == '' )
  1305. {
  1306. continue;
  1307. }
  1308. //make sure the filter exists
  1309. $filter_name = trim($filter['0']);
  1310. if ( isset( $this->valid_filters[$filter_name] ) )
  1311. {
  1312. //if a native image filter is being used, make sure it exists with this installation
  1313. if ( $this->valid_filters[$filter_name] === 'imagefilter' && ! defined( 'IMG_FILTER_' . strtoupper($filter_name) ) )
  1314. {
  1315. continue;
  1316. }
  1317. //add the filter
  1318. $filters[] = $filter;
  1319. }
  1320. }
  1321. }
  1322. unset( $filter_array );
  1323. $this->filters = $filters;
  1324. unset( $filters );
  1325. //get crop param
  1326. if ( ! is_array( $this->crop ) )
  1327. {
  1328. $this->crop = array( $this->crop );
  1329. }
  1330. $crop = $this->crop;
  1331. $this->do_crop = ( isset( $crop[0] ) && $crop[0] === true );
  1332. //get border param
  1333. $border = $this->border;
  1334. if ( $border != false && isset( $border[0] ) && is_numeric( $border[0] ) && $border[0] > 0 )
  1335. {
  1336. $this->check_size = true;
  1337. if ( isset( $border[1] ) )
  1338. {
  1339. $temp = Ce_image_tools::hex_cleanup( $border[1] );
  1340. if ( $temp != '' )
  1341. {
  1342. $this->border[1] = $temp;
  1343. }
  1344. else
  1345. {
  1346. $this->border[1] = 'ffffff';
  1347. }
  1348. }
  1349. else
  1350. {
  1351. $this->border[1] = 'ffffff';
  1352. }
  1353. }
  1354. else
  1355. {
  1356. $this->border[0] = 0;
  1357. $this->border[1] = 'ffffff';
  1358. }
  1359. //get rounded_corners param
  1360. $corners = $this->rounded_corners;
  1361. $corners_array = array();
  1362. if ( is_array( $corners ) && count( $corners ) > 0 )
  1363. {
  1364. if ( ! is_array( $corners[0] ) )
  1365. {
  1366. $corners = array( $corners );
  1367. }
  1368. foreach ( $corners as $corner )
  1369. {
  1370. //get the position
  1371. if ( isset( $corner[0] ) && in_array( $corner[0], $this->corner_options ) )
  1372. {
  1373. $corner_array = array();
  1374. $pos = $corner[0];
  1375. //get the radius
  1376. $rad = 15;
  1377. if ( isset( $corner[1] ) && is_numeric( $corner[1] ) )
  1378. {
  1379. $rad = $corner[1];
  1380. }
  1381. //get the color (will end up being: hex, or '')
  1382. $col = '';
  1383. if ( isset( $corner[2] ) )
  1384. {
  1385. $col = Ce_image_tools::hex_cleanup( $corner[2] );
  1386. }
  1387. if ( $pos == 'all' )
  1388. {
  1389. if ( $rad > 0 )
  1390. {
  1391. $corners_array['tl'] = array( $rad, $col );
  1392. $corners_array['tr'] = array( $rad, $col );
  1393. $corners_array['bl'] = array( $rad, $col );
  1394. $corners_array['br'] = array( $rad, $col );
  1395. }
  1396. else
  1397. {
  1398. $corners_array = array();
  1399. }
  1400. }
  1401. else
  1402. {
  1403. if ( $rad > 0 )
  1404. {
  1405. $corners_array[$pos] = array( $rad, $col );
  1406. }
  1407. else
  1408. {
  1409. unset( $corners_array[$pos] );
  1410. }
  1411. }
  1412. }
  1413. }
  1414. }
  1415. $this->corners_array = $corners_array;
  1416. $this->corners = ( count($this->corners_array) > 0 );
  1417. //get flip param
  1418. if ( ! is_array( $this->flip ) && in_array( $this->flip, $this->flip_options ) )
  1419. {
  1420. $this->flip = array( $this->flip );
  1421. }
  1422. $flip = $this->flip;
  1423. if ( is_array( $flip ) )
  1424. {
  1425. foreach( $flip as $i => $f )
  1426. {
  1427. if ( ! in_array( $f, $this->flip_options ) )
  1428. {
  1429. unset( $flip[$i] );
  1430. }
  1431. }
  1432. //remove duplicate values
  1433. $this->flip = array_unique( $flip );
  1434. if ( count( $flip ) > 0 )
  1435. {
  1436. $this->flip = $flip;
  1437. }
  1438. else
  1439. {
  1440. $this->flip = false;
  1441. }
  1442. }
  1443. else
  1444. {
  1445. $this->flip = false;
  1446. }
  1447. //get reflection param
  1448. $ref = $this->reflection;
  1449. $reflection_array = array();
  1450. if ( is_array( $ref ) && count( $ref ) > 0 )
  1451. {
  1452. $reflection_array[0] = ( isset( $ref[0] ) && is_numeric( $ref[0] ) ) ? trim( $ref[0] ) : 0; //gap
  1453. $reflection_array[1] = ( isset( $ref[1]) && is_numeric( $ref[1]) ) ? trim( $ref[1] ): 80; //start opacity
  1454. $reflection_array[2] = ( isset( $ref[2]) && is_numeric( $ref[2]) ) ? trim( $ref[2] ): 0; //end opacity
  1455. $reflection_array[3] = ( isset( $ref[3]) && is_numeric( str_replace( '%', '', $ref[3] ) ) ) ? trim( $ref[3] ) : '50%'; //ref_height
  1456. }
  1457. $this->reflection_array = $reflection_array;
  1458. if ( count($this->reflection_array) > 0 )
  1459. {
  1460. $this->reflection = true;
  1461. $this->check_size = true;
  1462. }
  1463. //get rotate param
  1464. if ( $this->rotate != 0 && is_numeric( $this->rotate ) && $this->rotate % 360 != 0 )
  1465. {
  1466. $this->check_size = true;
  1467. }
  1468. else
  1469. {
  1470. $this->rotate = false;
  1471. }
  1472. //get overwrite_cache param
  1473. if ( ! is_bool( $this->overwrite_cache ) )
  1474. {
  1475. $this->overwrite_cache = false;
  1476. }
  1477. //get hash_filename param
  1478. if ( ! is_bool( $this->hash_filename ) )
  1479. {
  1480. $this->hash_filename = false;
  1481. }
  1482. //get save_type param
  1483. $save_type = trim( $this->save_type );
  1484. if ( $save_type == '' )
  1485. {
  1486. $save_type = ( $this->image_data_orig['ext'] ) ? $this->image_data_orig['ext'] : $this->get_type_orig();
  1487. }
  1488. if ( ! in_array( strtolower( $save_type ), $this->image_types ) )
  1489. {
  1490. $save_type = 'jpg';
  1491. }
  1492. $this->image_data['ext'] = $save_type;
  1493. //check quality to be valid
  1494. if ( ! is_numeric( $this->quality ) || $this->quality < 0 || $this->quality > 100 )
  1495. {
  1496. $this->quality = 100;
  1497. }
  1498. //---------- find new dimensions ----------
  1499. if ( $this->windows_dev )
  1500. {
  1501. $this->src = str_replace('/', '\\', $this->src);
  1502. }
  1503. $result = $this->get_dimensions();
  1504. if ( ! $result )
  1505. {
  1506. return false;
  1507. }
  1508. //prepare the text
  1509. if ( $this->text != false )
  1510. {
  1511. $this->get_text_preference();
  1512. }
  1513. return true;
  1514. }
  1515. /**
  1516. * Determines which text preference, if any, meets the minimum dimensions requirement and sets it as the class' text property.
  1517. *
  1518. * @return void
  1519. */
  1520. function get_text_preference()
  1521. {
  1522. //if a single text array, wrap it in an array
  1523. if ( is_array( $this->text ) && ! is_array( $this->text[0] ) )
  1524. {
  1525. $this->text = array( $this->text );
  1526. }
  1527. //---------- get preference by minimum dimension ----------
  1528. //let's see what preferences are being passed in for minimum dimensions, and see if we can find one that works
  1529. $final_pref_ind = -1;
  1530. foreach ( $this->text as $index => $text_pref )
  1531. {
  1532. //break up the chunks
  1533. if ( is_array( $text_pref ) && count( $text_pref ) > 1 )
  1534. {
  1535. //size limits should be here
  1536. if ( count( $text_pref[1] ) == 2 )
  1537. {
  1538. list( $min_w, $min_h ) = $text_pref[1];
  1539. //check the dimensions
  1540. if ( $this->do_crop == false )
  1541. {
  1542. if ( ($min_w == 0 || $min_w <= $this->width_final) && ($min_h == 0 || $min_h <= $this->height_final) )
  1543. {
  1544. //this pref will work
  1545. $final_pref_ind = $index;
  1546. break 1;
  1547. }
  1548. }
  1549. else
  1550. {
  1551. if ( ($min_w == 0 || $min_w <= $this->width_desired) && ($min_h == 0 || $min_h <= $this->height_desired) )
  1552. {
  1553. //this pref will work
  1554. $final_pref_ind = $index;
  1555. break 1;
  1556. }
  1557. }
  1558. }
  1559. else
  1560. {
  1561. //both params are not there...we'll assume this is the one they wanted
  1562. $final_pref_ind = $index;
  1563. break 1;
  1564. return;
  1565. }
  1566. }
  1567. else
  1568. {
  1569. //no size limits found, so this pref will do
  1570. $final_pref_ind = $index;
  1571. break 1;
  1572. }
  1573. }
  1574. if ( $final_pref_ind == -1 )
  1575. {
  1576. //we could not find a preference that fit the specified limits, so no watermark
  1577. $this->text = false;
  1578. return;
  1579. }
  1580. $this->text = $this->text[$final_pref_ind];
  1581. if ( ! isset( $this->text[0] ) || trim( $this->text[0] == '' ) ) //there is no text
  1582. {
  1583. $this->text = false;
  1584. return;
  1585. }
  1586. }
  1587. /**
  1588. * Validate and prepare the text settings.
  1589. *
  1590. * @return void
  1591. */
  1592. function prepare_text()
  1593. {
  1594. //0 - the text
  1595. $this->text[0] = preg_replace( '@(\r\n|\r|\n)@Usmi', ' \n ', $this->text[0] ); //convert all line breaks to ' \n '
  1596. //1 - minimum dimensions (these will be removed later)
  1597. //2 - font size
  1598. if ( ! isset( $this->text[2] ) || ! is_numeric( $this->text[2] ) || $this->text[2] < 0 )
  1599. {
  1600. $this->text[2] = 12;
  1601. }
  1602. //3 - line height
  1603. if ( isset( $this->text[3] ) )
  1604. {
  1605. if ( ! is_numeric( $this->text[3] ) )
  1606. {
  1607. if ( strpos( $this->text[3], '%' ) !== false ) //% of the font size
  1608. {
  1609. $this->text[3] = str_replace( '%', '', $this->text[3] ); //remove the %
  1610. if ( is_numeric( $this->text[3] ) ) //if numeric determine the % of font size
  1611. {
  1612. $this->text[3] = round( $this->text[2] * $this->text[3] * .01 );
  1613. }
  1614. }
  1615. }
  1616. }
  1617. if ( ! isset( $this->text[3] ) || ! is_numeric( $this->text[3] ) || $this->text[3] < 0 ) //default
  1618. {
  1619. $this->text[3] = round( $this->text[2] * 1.25 ); //default to 1.25 of the font size
  1620. }
  1621. //4 - color
  1622. if ( isset( $this->text[4] ) )
  1623. {
  1624. $this->text[4] = Ce_image_tools::hex_cleanup( $this->text[4] );
  1625. }
  1626. if ( ! isset( $this->text[4] ) || $this->text[4] == '' )
  1627. {
  1628. $this->text[4] = 'ffffff';
  1629. }
  1630. //5 - font - will default to the packaged heros-bold.ttf if no font is included
  1631. $font = str_replace( '\\', '/', dirname(__FILE__) . '/../fonts/heros-bold.ttf' );
  1632. if ( isset( $this->text[5] ) && $this->text[5] != '' )
  1633. {
  1634. //clean the filename
  1635. $orig = $this->EE->security->xss_clean( $this->text[5] );
  1636. if ( @file_exists( $orig ) === true ) //full server path
  1637. {
  1638. $this->debug_messages[] = "The full server font path '$orig' was found.";
  1639. //create full path for source
  1640. $font = $orig;
  1641. }
  1642. else
  1643. {
  1644. $this->debug_messages[] = "The full server font path '$orig' was not found, checking if it is a default font.";
  1645. $temp = $this->EE->security->xss_clean( $this->remove_duplicate_slashes( str_replace( '\\', '/', dirname(__FILE__) . '/../fonts/' . $orig ) ) );
  1646. if ( @file_exists( $temp ) === true ) //a default font
  1647. {
  1648. $this->debug_messages[] = "The font '$temp' was found in the default folder.";
  1649. $font = $temp;
  1650. }
  1651. else //relative to document root
  1652. {
  1653. $this->debug_messages[] = "The font '$temp' does not appear to be in the default folder.";
  1654. $temp = $this->EE->security->xss_clean( $this->remove_duplicate_slashes( $this->base . '/' . $orig ) );
  1655. if ( @file_exists( $temp ) === true )
  1656. {
  1657. $this->debug_messages[] = "The font '$temp' was found as a relative path.";
  1658. $font = $temp;
  1659. }
  1660. else
  1661. {
  1662. $this->debug_messages[] = "The font '$temp' could not be found by its relative path, the default font will be used.";
  1663. }
  1664. }
  1665. }
  1666. }
  1667. $this->text[5] = realpath( $font );
  1668. //6 - text alignment
  1669. if ( ! isset( $this->text[6] ) || ! in_array( $this->text[6], $this->x_options ) )
  1670. {
  1671. $this->text[6] = 'center';
  1672. }
  1673. //7 - width adjustment
  1674. if ( ! isset( $this->text[7] ) || ! is_numeric( $this->text[7] ) )
  1675. {
  1676. $this->text[7] = 0;
  1677. }
  1678. //8 - position
  1679. $position = array( 'center', 'center' );
  1680. if ( isset( $this->text[8] ) && is_array( $this->text[8] ) )
  1681. {
  1682. if ( isset( $this->text[8][0] ) && in_array( $this->text[8][0], $this->x_options ) )
  1683. {
  1684. $position[0] = $this->text[8][0];
  1685. }
  1686. if ( isset( $this->text[8][1] ) && in_array( $this->text[8][1], $this->y_options ) )
  1687. {
  1688. $position[1] = $this->text[8][1];
  1689. }
  1690. }
  1691. $this->text[8] = $position;
  1692. //9 - offset
  1693. $offset = array( 0, 0 );
  1694. if ( isset( $this->text[9] ) && is_array( $this->text[9] ) )
  1695. {
  1696. if ( isset( $this->text[9][0] ) && is_numeric( $this->text[9][0] ) )
  1697. {
  1698. $offset[0] = $this->text[9][0];
  1699. }
  1700. if ( isset( $this->text[9][1] ) && is_numeric( $this->text[9][1] ) )
  1701. {
  1702. $offset[1] = $this->text[9][1];
  1703. }
  1704. }
  1705. $this->text[9] = $offset;
  1706. //10 - opacity
  1707. if ( ! isset( $this->text[10] ) || ! is_numeric( $this->text[10] ) || $this->text[10] < 0 || $this->text[10] > 100 )
  1708. {
  1709. $this->text[10] = 100;
  1710. }
  1711. //11 - shadow color
  1712. //color
  1713. if ( isset( $this->text[11] ) )
  1714. {
  1715. $this->text[11] = Ce_image_tools::hex_cleanup( $this->text[11] );
  1716. }
  1717. else
  1718. {
  1719. $this->text[11] = '000000';
  1720. }
  1721. //12 - shadow offset
  1722. $offset = array( 1, 1 );
  1723. if ( isset( $this->text[12] ) && is_array( $this->text[12] ) )
  1724. {
  1725. if ( isset( $this->text[12][0] ) && is_numeric( $this->text[12][0] ) )
  1726. {
  1727. $offset[0] = $this->text[12][0];
  1728. }
  1729. if ( isset( $this->text[12][1] ) && is_numeric( $this->text[12][1] ) )
  1730. {
  1731. $offset[1] = $this->text[12][1];
  1732. }
  1733. }
  1734. $this->text[12] = $offset;
  1735. //13 - shadow opacity
  1736. if ( ! isset( $this->text[13] ) || ! is_numeric( $this->text[13] ) || $this->text[13] < 0 || $this->text[13] > 100 )
  1737. {
  1738. $this->text[13] = 50;
  1739. }
  1740. //remove the minimum dimension, as it will no longer be needed
  1741. unset( $this->text[1] );
  1742. }
  1743. // ---------------------------------------------------------------------------- CREATE ----------------------------------------------------------------------------
  1744. /**
  1745. * Opens and manipulates an image.
  1746. *
  1747. * @param string|resource $src The image source. Can be a relative path to document root, a full server (absolute) path, a URL (remote or local), an HTML snippet, or a GD2 image resource.
  1748. * @param array $temp_settings The settings that will temporary override the previously set default settings, for this image only.
  1749. * @param bool $save Whether or not to save the image after it is 'made.'
  1750. * @return bool Will return true on success and false on failure.
  1751. */
  1752. public function make( $src = '', $temp_settings = array(), $save = true )
  1753. {
  1754. if ( $this->open( $src, $temp_settings ) === false )
  1755. {
  1756. return false;
  1757. }
  1758. //prepare the settings
  1759. if ( $this->prep_settings() == false )
  1760. {
  1761. return false;
  1762. }
  1763. if ( $save == true )
  1764. {
  1765. //get remote_cache_time param
  1766. if ( $this->is_remote )
  1767. {
  1768. if ( ! is_numeric( $this->remote_cache_time ) || $this->remote_cache_time < 0 )
  1769. {
  1770. $this->remote_cache_time = -1;
  1771. }
  1772. }
  1773. //---------- check the cache ----------
  1774. $is_cached = $this->is_cached();
  1775. if ( ! $is_cached || $this->overwrite_cache ) //the image is not cached
  1776. {
  1777. if ( $is_cached && $this->same_as_source && ! $this->allow_overwrite_original ) //don't overwrite cache
  1778. {
  1779. $this->debug_messages[] = "The source image '$this->path_final' will not be overwritten in order to prevent image degradation.";
  1780. }
  1781. else //not cached
  1782. {
  1783. $this->debug_messages[] = "The image '$this->path_final' is not cached.";
  1784. if ( $this->open_real( true ) == false )
  1785. {
  1786. return false;
  1787. }
  1788. //image creation
  1789. if ( $this->create() == false )
  1790. {
  1791. return false;
  1792. }
  1793. //return results
  1794. if ( ! $this->save( $this->image_data['ext'] ) )
  1795. {
  1796. return false;
  1797. }
  1798. $this->final_action = 'saved';
  1799. }
  1800. //make the final path the source path
  1801. $this->src = $this->path_final;
  1802. $this->is_data_ready = true;
  1803. $width = round($this->width_final);
  1804. $height = round($this->height_final);
  1805. }
  1806. else //the image is cached
  1807. {
  1808. $this->debug_messages[] = "The image '$this->path_final' is cached.";
  1809. //make the final path the source path
  1810. $this->src = $this->path_final;
  1811. $this->final_action = 'cached';
  1812. if ( ! $this->check_size ) //the dimension already determined should work, and the cached file doesn't need to be touched
  1813. {
  1814. $width = ( $this->do_crop == false ) ? round($this->width_final) : round($this->width_desired);
  1815. $height = ( $this->do_crop == false ) ? round($this->height_final) : round( $this->height_desired );
  1816. }
  1817. else //check cached image, since it may be different from the requested size (from rotating or reflecting)
  1818. {
  1819. $data = @getimagesize( $this->src );
  1820. if ( $data )
  1821. {
  1822. $width = $data[0];
  1823. $height = $data[1];
  1824. }
  1825. else
  1826. {
  1827. $width = '';
  1828. $height = '';
  1829. $this->debug_messages[] = "Could not get dimensions of the cached image '$this->src'.";
  1830. }
  1831. }
  1832. }
  1833. //---------- return data ----------
  1834. $this->image_data['width'] = $width;
  1835. $this->image_data['height'] = $height;
  1836. }
  1837. else //elected not to save
  1838. {
  1839. if ( $this->open_real() == false )
  1840. {
  1841. return false;
  1842. }
  1843. //image creation
  1844. if ( $this->create() == false )
  1845. {
  1846. return false;
  1847. }
  1848. $this->is_open_ready = true;
  1849. $this->is_open = true;
  1850. $this->src = '';
  1851. }
  1852. return true;
  1853. }
  1854. /**
  1855. * Determines the filename that should be created for the manipulated image and whether or not it is cached.
  1856. *
  1857. * @return bool Returns true if the image is cached and does not need to be recreated, false otherwise.
  1858. */
  1859. private function is_cached()
  1860. {
  1861. $is_jpg = (strtolower( $this->image_data['ext'] ) == 'jpg' || strtolower( $this->image_data['ext'] ) == 'jpeg');
  1862. $info = '';
  1863. $info .= ( $is_jpg && $this->quality != 100 ) ? '_' . $this->quality : ''; //quality
  1864. $info .= ($this->bg_color != '') ? '_' . $this->bg_color : '';
  1865. $info .= ( $this->bg_color == '' && $this->bg_color_default != '' && $this->bg_color_default != 'ffffff' && $is_jpg ) ? '_dbg-' . $this->bg_color_default : '';
  1866. //filters
  1867. if ( is_array($this->filters) )
  1868. {
  1869. foreach ( $this->filters as $filter )
  1870. {
  1871. foreach ($filter as $index => $piece)
  1872. {
  1873. if ( $index == '0' )
  1874. {
  1875. //only add the first 3 characters of the filter name to the info, to cut down on length
  1876. $info .= substr( str_replace('img_filter', '',strtolower( $piece )), 0, 4 );
  1877. }
  1878. else
  1879. {
  1880. if ( is_array( $piece ) )
  1881. {
  1882. $piece = Ce_image_tools::recursive_implode( '_', $piece );
  1883. }
  1884. $piece = (string) $piece;
  1885. $info .= '-' . strtolower( $piece );
  1886. }
  1887. }
  1888. }
  1889. }
  1890. //flip
  1891. if ( $this->flip !== false )
  1892. {
  1893. $info .= '_f' . implode( '', $this->flip );
  1894. }
  1895. //watermark
  1896. if ( $this->watermark != '' )
  1897. {
  1898. $info .= '_' . Ce_image_tools::recursive_implode( '|', $this->watermark );
  1899. }
  1900. //text
  1901. if ( $this->text != '' && is_array( $this->text ) && count( $this->text ) > 0 )
  1902. {
  1903. //add to the info array
  1904. $info .= '_' . substr( md5( Ce_image_tools::recursive_implode( '|', $this->text ) ), 0, 16);
  1905. }
  1906. //borders
  1907. if ( $this->border[0] > 0 )
  1908. {
  1909. $info .= '_bor' . $this->border[0] . '_' . $this->border[1];
  1910. }
  1911. //rounded corners
  1912. if ( $this->corners === true )
  1913. {
  1914. $corners_all = false;
  1915. //check to see if all of the corners are the same
  1916. if ( count( $this->corners_array ) == 4 )
  1917. {
  1918. if ( array_diff( $this->corners_array['tl'], $this->corners_array['tr'], $this->corners_array['bl'], $this->corners_array['br'] ) == array() )
  1919. {
  1920. $corners_all = true;
  1921. }
  1922. }
  1923. if ( $corners_all )
  1924. {
  1925. $info .= '_all_' . $this->corners_array['tl'][0];
  1926. if ($this->corners_array['tl'][1] != '')
  1927. {
  1928. $info .= '_' . $this->corners_array['tl'][1];
  1929. }
  1930. }
  1931. else
  1932. {
  1933. foreach( $this->corners_array as $pos => $corner )
  1934. {
  1935. $info .= '_' . $pos . '_' . $corner[0];
  1936. if ( $corner[1] != '')
  1937. {
  1938. $info .= '_' . $corner[1];
  1939. }
  1940. }
  1941. }
  1942. }
  1943. //reflection
  1944. if ( $this->reflection === true )
  1945. {
  1946. $info .= '_ref' . implode('-', $this->reflection_array);
  1947. }
  1948. //rotate
  1949. if ( $this->rotate !== false )
  1950. {
  1951. $info .= '_rot' . $this->rotate;
  1952. }
  1953. //clean up info a bit
  1954. $info = str_replace( array('/','|',',','center','top','bottom','left','right','yes','no','%','#'), array('','_','_','c','t','b','l','r','y','n','p',''), $info);
  1955. //ensure the unique parameter is set correctly
  1956. if ( ! in_array( $this->unique, array( 'filename', 'directory_name', 'none' ) ) )
  1957. {
  1958. $this->unique = 'filename';
  1959. }
  1960. $width = (($this->do_crop === true) ? round($this->width_desired) : round($this->width_final));
  1961. $height = (($this->do_crop === true) ? round($this->height_desired) : round($this->height_final));
  1962. //determine whether or not the image name needs to change
  1963. if ( $info == '' && $this->image_data_orig['width'] == $width && $this->image_data_orig['height'] == $height && $this->image_data['ext'] == $this->image_data_orig['ext'] && $this->hash_filename == false && $this->is_above_root && $this->filename == $this->filename_orig && $this->filename_prefix == '' && $this->filename_suffix == '' ) //this is the original
  1964. {
  1965. $this->path_final = $this->get_original_server_path();
  1966. $this->same_as_source = true;
  1967. }
  1968. else if ($this->unique == 'none' && $this->hide_relative_path && ($this->image_data_orig['width'] != $width || $this->image_data_orig['height'] != $height) && $this->filename == $this->filename_orig && $this->image_data['ext'] == $this->image_data_orig['ext'] && $this->hash_filename == false ) //the user may be trying to overwrite the original, once
  1969. {
  1970. $this->path_final = $this->get_original_server_path();
  1971. $this->same_as_source = false;
  1972. return false;
  1973. }
  1974. else
  1975. {
  1976. $info .= ($this->allow_scale_larger === true) ? '_s' : ''; //scale larger
  1977. $info .= ($this->do_crop === true) ? '_c' . Ce_image_tools::recursive_implode('_', $this->crop) : ''; //crop
  1978. $info = $width . '_' . $height . $info;
  1979. //if the filename is unique (default)
  1980. if ($this->unique == 'filename' && $info != '')
  1981. {
  1982. //concatenate filename to original filename
  1983. $filename = $this->filename . '_' . $info;
  1984. }
  1985. else
  1986. {
  1987. $filename = $this->filename;
  1988. }
  1989. //hash if specified
  1990. if ($this->hash_filename)
  1991. {
  1992. $filename = sha1($filename);
  1993. }
  1994. $this->filename_final = $filename;
  1995. //add the prefix
  1996. if (!empty($this->filename_prefix))
  1997. {
  1998. $filename = $this->filename_prefix . $filename;
  1999. }
  2000. //add the suffix
  2001. if (!empty($this->filename_suffix))
  2002. {
  2003. $filename .= $this->filename_suffix;
  2004. }
  2005. //add file extension
  2006. $filename .= '.' . $this->image_data['ext'];
  2007. //if the directory name is not unique
  2008. if ($this->unique != 'directory_name')
  2009. {
  2010. $this->path_final = $this->cache_full . $filename; //final path
  2011. }
  2012. else
  2013. {
  2014. $this->cache_full = $this->cache_full . $info . '/';
  2015. $this->relative = $this->relative . $info . '/';
  2016. if ($this->windows_dev)
  2017. {
  2018. $this->cache_full = str_replace('/', '\\', $this->cache_full);
  2019. }
  2020. $this->path_final = $this->cache_full . $filename; //final path
  2021. }
  2022. $this->filename = $filename; //class filename
  2023. }
  2024. if ( ! @file_exists( $this->path_final ) ) //not cached if the file doesn't exist
  2025. {
  2026. return false;
  2027. }
  2028. $file_m_time = @filemtime( $this->path_final ); //filetime of cached image
  2029. return ( ( $file_m_time >= $this->filetime_orig ) && ( $file_m_time >= $this->filetime_wm ) ); //true if the file was made after the source image & optional watermark image, false otherwise
  2030. }
  2031. /**
  2032. * Attempts to resolve any relative dimensions to actual dimensions and ensure that a valid number is returned.
  2033. *
  2034. * @param int|string $dimension
  2035. * @param int|string $actual
  2036. * @return int
  2037. */
  2038. private function resolve_dimension( $dimension, $actual )
  2039. {
  2040. if ( strpos( $dimension, '%' ) !== false )
  2041. {
  2042. $dimension = str_replace( '%', '', $dimension );
  2043. $dimension = ( is_numeric( $dimension ) ) ? round( $actual * $dimension * .01 ) : 0;
  2044. }
  2045. if ( ! is_numeric( $dimension ) || $dimension < 0 )
  2046. {
  2047. $dimension = 0;
  2048. }
  2049. return $dimension;
  2050. }
  2051. /**
  2052. * Attempts to figure out the final width and height of the manipulated image.
  2053. *
  2054. * @return void
  2055. */
  2056. private function get_dimensions()
  2057. {
  2058. $this->filetime_orig = @filemtime( $this->src );
  2059. $width_initial = $this->image_data['width'];
  2060. $height_initial = $this->image_data['height'];
  2061. $this->width_initial = $width_initial;
  2062. $this->height_initial = $height_initial;
  2063. $this->width_final = $width_initial;
  2064. $this->height_final = $height_initial;
  2065. $this->width_desired = $width_initial;
  2066. $this->height_desired = $height_initial;
  2067. //resolve any relative dimensions and ensure all dimensions are usable
  2068. $width = $this->resolve_dimension( $this->width, $width_initial );
  2069. $min_width = $this->resolve_dimension( $this->min_width, $width_initial );
  2070. $max_width = $this->resolve_dimension( $this->max_width, $width_initial );
  2071. $height = $this->resolve_dimension( $this->height, $height_initial );
  2072. $min_height = $this->resolve_dimension( $this->min_height, $height_initial );
  2073. $max_height = $this->resolve_dimension( $this->max_height, $height_initial );
  2074. //min width
  2075. if ( ! empty($min_width) )
  2076. {
  2077. if ( empty($width) )
  2078. {
  2079. if ( $min_width > $width_initial )
  2080. {
  2081. $width = $min_width;
  2082. }
  2083. }
  2084. else if ($min_width > $width)
  2085. {
  2086. $width = $min_width;
  2087. }
  2088. }
  2089. //min height
  2090. if ( ! empty($min_height) )
  2091. {
  2092. if ( empty($height) )
  2093. {
  2094. if ( $min_height > $height_initial )
  2095. {
  2096. $height = $min_height;
  2097. }
  2098. }
  2099. else if ($min_height > $height)
  2100. {
  2101. $height = $min_height;
  2102. }
  2103. }
  2104. //max width
  2105. if ( ! empty($max_width) && (empty($width) || $max_width < $width) )
  2106. {
  2107. $width = $max_width;
  2108. }
  2109. //max height
  2110. if ( ! empty($max_height) && (empty($height) || $max_height < $height) )
  2111. {
  2112. $height = $max_height;
  2113. }
  2114. //if they don't want the image to scale up, and the desired dimensions are larger than the original, just return the original size
  2115. if ( ! $this->allow_scale_larger )
  2116. {
  2117. if ($width > $width_initial)
  2118. {
  2119. $width = $width_initial;
  2120. }
  2121. else if ($height > $height_initial)
  2122. {
  2123. $height = $height_initial;
  2124. }
  2125. }
  2126. $width_new = $width;
  2127. $height_new = $height;
  2128. $x_offset = 0;
  2129. $y_offset = 0;
  2130. //if width and height are 0, just return original size
  2131. if ( ($width == 0) && ($height == 0) )
  2132. {
  2133. if ( $this->watermark )
  2134. {
  2135. $this->get_watermark_dimensions();
  2136. }
  2137. return true;
  2138. }
  2139. //calculate new dimensions
  2140. if ( $this->do_crop == false ) //resize to fit within dimensions
  2141. {
  2142. $ratio_x = ($width == 0) ? 0 : round( $width_initial / $width, 2 );
  2143. $ratio_y = ($height == 0 ) ? 0 : round( $height_initial / $height, 2);
  2144. if ( $ratio_x < $ratio_y )
  2145. {
  2146. $width_new = $width_initial / ( $height_initial / $height );
  2147. }
  2148. else
  2149. {
  2150. $height_new = $height_initial / ( $width_initial / $width );
  2151. }
  2152. $this->x_offset = $x_offset;
  2153. $this->y_offset = $y_offset;
  2154. $this->width_final = $width_new;
  2155. $this->height_final = $height_new;
  2156. if ( $this->watermark != '')
  2157. {
  2158. $this->get_watermark_dimensions();
  2159. }
  2160. return true;
  2161. }
  2162. else //crop to dimensions
  2163. {
  2164. //if width or height are 0, determine what the other dimension should be
  2165. if ($width == 0)
  2166. {
  2167. $width = $width_initial * ($height / $height_initial);
  2168. $width_new = $width;
  2169. }
  2170. if ($height == 0)
  2171. {
  2172. $height = $height_initial * ($width / $width_initial);
  2173. $height_new = $height;
  2174. }
  2175. if ( $this->allow_scale_larger == false )
  2176. {
  2177. if ($width > $width_initial)
  2178. {
  2179. $width = $width_initial;
  2180. }
  2181. else if ($height > $height_initial)
  2182. {
  2183. $height = $height_initial;
  2184. }
  2185. $width_new = $width;
  2186. $height_new = $height;
  2187. }
  2188. $this->width_desired = round( $width );
  2189. $this->height_desired = round( $height );
  2190. //defaults
  2191. $smart_scale = true;
  2192. $crop_pos_x = 'center';
  2193. $crop_pos_y = 'center';
  2194. $crop_offset_x = 0;
  2195. $crop_offset_y = 0;
  2196. $crop_count = count( $this->crop );
  2197. //positions
  2198. if ( $crop_count > 1 )
  2199. {
  2200. $temp = $this->crop[1];
  2201. if ( is_array( $temp ) && count( $temp ) == 2 )
  2202. {
  2203. list( $temp_x, $temp_y ) = $temp;
  2204. if ( in_array( $temp_x, $this->x_options) )
  2205. {
  2206. $crop_pos_x = $temp_x;
  2207. }
  2208. if ( in_array( $temp_y, $this->y_options) )
  2209. {
  2210. $crop_pos_y = $temp_y;
  2211. }
  2212. }
  2213. }
  2214. //offsets
  2215. if ( $crop_count > 2 )
  2216. {
  2217. $temp = $this->crop[2];
  2218. if ( is_array( $temp ) && count( $temp ) == 2 )
  2219. {
  2220. list( $temp_x, $temp_y ) = $temp;
  2221. if ( is_numeric( $temp_x ) )
  2222. {
  2223. $crop_offset_x = $temp_x;
  2224. }
  2225. if ( is_numeric( $temp_y ) )
  2226. {
  2227. $crop_offset_y = $temp_y;
  2228. }
  2229. }
  2230. }
  2231. //smart scale
  2232. if ( $crop_count > 3 )
  2233. {
  2234. $smart_scale = $this->crop[3];
  2235. }
  2236. //smart scale is enabled, figure out crop info
  2237. if ($smart_scale)
  2238. {
  2239. $ratio_x = ($width_initial == 0) ? 0 : round( $width_initial / $width_new, 2 );
  2240. $ratio_y = ($height_initial == 0 ) ? 0 : round( $height_initial / $height_new, 2);
  2241. //determine new dimensions
  2242. if ( $ratio_x > $ratio_y )
  2243. {
  2244. $width_new = $width_initial / ( $height_initial / $height );
  2245. }
  2246. else
  2247. {
  2248. $height_new = $height_initial / ($width_initial / $width);
  2249. }
  2250. switch( $crop_pos_x )
  2251. {
  2252. case 'left':
  2253. $x_offset = 0;
  2254. break;
  2255. case 'right':
  2256. $x_offset = $width - $width_new;
  2257. break;
  2258. default:
  2259. $x_offset = - ($width_new - $width) * .5;
  2260. }
  2261. switch( $crop_pos_y )
  2262. {
  2263. case 'top':
  2264. $y_offset = 0;
  2265. break;
  2266. case 'bottom':
  2267. $y_offset = $height - $height_new;
  2268. break;
  2269. default:
  2270. $y_offset = - ($height_new - $height) * .5;
  2271. }
  2272. $this->x_offset = $x_offset + $crop_offset_x;
  2273. $this->y_offset = $y_offset + $crop_offset_y;
  2274. }
  2275. $this->width_final = round( $width_new );
  2276. $this->height_final = round( $height_new );
  2277. //smart scale is disabled, figure out crop info
  2278. if (! $smart_scale ) //no smart scale
  2279. {
  2280. $this->x_offset = 0;
  2281. $this->y_offset = 0;
  2282. switch( $crop_pos_x )
  2283. {
  2284. case 'left':
  2285. $x_offset = 0;
  2286. break;
  2287. case 'right':
  2288. $x_offset = $width_initial - $width_new;
  2289. break;
  2290. default:
  2291. $x_offset = - ($width_new - $width_initial) * .5;
  2292. }
  2293. switch( $crop_pos_y )
  2294. {
  2295. case 'top':
  2296. $y_offset = 0;
  2297. break;
  2298. case 'bottom':
  2299. $y_offset = $height_initial - $height_new;
  2300. break;
  2301. default:
  2302. $y_offset = - ($height_new - $height_initial) * .5;
  2303. }
  2304. $this->src_x_offset = $x_offset + $crop_offset_x;
  2305. $this->src_y_offset = $y_offset + $crop_offset_y;
  2306. $this->width_initial = $width_new;
  2307. $this->height_initial = $height_new;
  2308. }
  2309. if ( $this->watermark != '')
  2310. {
  2311. $this->get_watermark_dimensions();
  2312. }
  2313. return true;
  2314. }
  2315. }
  2316. /**
  2317. * Determines whether or not a suitable watermark exists and determines its dimensions if it does.
  2318. *
  2319. * @return void
  2320. */
  2321. private function get_watermark_dimensions()
  2322. {
  2323. //if a single watermark, wrap it in an array
  2324. if ( is_array( $this->watermark ) && ! is_array( $this->watermark[0] ) )
  2325. {
  2326. $this->watermark = array( $this->watermark );
  2327. }
  2328. //lets see what preferences are being passed in for watermarking, and see if we can find one that works
  2329. $wm_prefs = $this->watermark;
  2330. $final_pref_ind = -1;
  2331. foreach ( $wm_prefs as $ind => $wm_pref )
  2332. {
  2333. //break up the chunks
  2334. if ( count( $wm_pref ) > 1 )
  2335. {
  2336. //size limits should be here
  2337. if ( count( $wm_pref[1] ) == 2 )
  2338. {
  2339. list( $min_w, $min_h ) = $wm_pref[1];
  2340. //check the dimensions
  2341. if ( $this->do_crop == false )
  2342. {
  2343. if ( ($min_w == 0 || $min_w <= $this->width_final) && ($min_h == 0 || $min_h <= $this->height_final) )
  2344. {
  2345. //this pref will work
  2346. $final_pref_ind = $ind;
  2347. break 1;
  2348. }
  2349. }
  2350. else
  2351. {
  2352. if ( ($min_w == 0 || $min_w <= $this->width_desired) && ($min_h == 0 || $min_h <= $this->height_desired) )
  2353. {
  2354. //this pref will work
  2355. $final_pref_ind = $ind;
  2356. break 1;
  2357. }
  2358. }
  2359. }
  2360. else
  2361. {
  2362. //both params are not there...bad syntax, bail out
  2363. $this->watermark = '';
  2364. return;
  2365. }
  2366. }
  2367. else
  2368. {
  2369. //no size limits found, so this pref will do
  2370. $final_pref_ind = $ind;
  2371. break 1;
  2372. }
  2373. }
  2374. if ( $final_pref_ind == -1 )
  2375. {
  2376. //we could not find a preference that fit the specified limits, so no watermark
  2377. $this->watermark = '';
  2378. return;
  2379. }
  2380. //we found a watermark that will do, lets work our magic!
  2381. $this->watermark = $wm_prefs[$final_pref_ind];
  2382. //get the watermark source
  2383. $this->wm_array = $this->watermark;
  2384. $wm_src = $this->wm_array[0];
  2385. //check if URL
  2386. if ( substr( $wm_src, 0, 4 ) == 'http' )
  2387. {
  2388. $info = parse_url( $wm_src );
  2389. if ( $info === false )
  2390. {
  2391. return;
  2392. }
  2393. $wm_src = $info['path'];
  2394. }
  2395. //create full path for wm source
  2396. $temp = $this->remove_duplicate_slashes( $this->base . $wm_src );
  2397. //check to make sure the watermark source is readable
  2398. if ( @is_readable( $temp ) === true ) //relative path
  2399. {
  2400. //create full path for source
  2401. $wm_src = $this->remove_duplicate_slashes( realpath($temp) );
  2402. $wm_src = str_replace('\\', '/', $wm_src);
  2403. }
  2404. $this->wm_image_data['src'] = $wm_src;
  2405. if ( ! @is_readable( $this->wm_image_data['src'] ) )
  2406. {
  2407. $this->watermark = '';
  2408. $this->debug_messages[] = "Watermark file '$this->wm_image_data['src']' is not readable or does not exist.";
  2409. return;
  2410. }
  2411. else
  2412. {
  2413. $this->filetime_wm = @filemtime( $this->wm_image_data['src'] );
  2414. //get the image info
  2415. $wm_data = @getimagesize( $this->wm_image_data['src'] );
  2416. if ( ! $wm_data )
  2417. {
  2418. $this->watermark = '';
  2419. $this->debug_messages[] = 'Unknown image format for watermark.';
  2420. return;
  2421. }
  2422. else
  2423. {
  2424. $this->wm_image_data['width'] = $wm_data[0];
  2425. $this->wm_image_data['height'] = $wm_data[1];
  2426. $this->wm_image_data['type'] = $wm_data[2];
  2427. }
  2428. }
  2429. }
  2430. /**
  2431. * Does the actual heavy lifting to create a manipulated image.
  2432. *
  2433. * @return bool Returns true if the image is created successfully and false on failure.
  2434. */
  2435. private function create()
  2436. {
  2437. //round all values to get nice results
  2438. $this->width_final = round( $this->width_final );
  2439. $this->height_final = round( $this->height_final );
  2440. //create new image
  2441. if ( $this->do_crop == false )
  2442. {
  2443. $dest = imagecreatetruecolor( $this->width_final, $this->height_final );
  2444. }
  2445. else
  2446. {
  2447. $dest = imagecreatetruecolor( $this->width_desired, $this->height_desired );
  2448. }
  2449. //transparency and background color
  2450. if ( ($this->image_data_orig['type'] == 3 || $this->image_data_orig['type'] == 1) && strtolower( $this->image_data['ext'] ) == 'gif' ) //png/gif to gif
  2451. {
  2452. if ( $this->bg_color == false ) //no bg_color
  2453. {
  2454. $this->is_transparent = true;
  2455. //see if there is a transparent color index
  2456. $transparency_index = imagecolortransparent( $this->handle );
  2457. if ( $transparency_index >= 0 ) //there is a transparency index (gif)
  2458. {
  2459. $transparency_color = @imagecolorsforindex( $this->handle, $transparency_index);
  2460. if ( $this->image_data_orig['type'] == 1 ) //gif to gif
  2461. {
  2462. $transparency_index = imagecolorallocate($dest, $transparency_color['red'], $transparency_color['green'], $transparency_color['blue']);
  2463. }
  2464. else //png to gif
  2465. {
  2466. $transparency_index = imagecolorallocatealpha($dest, $transparency_color['red'], $transparency_color['green'], $transparency_color['blue'], 127);
  2467. }
  2468. imagefill($dest, 0, 0, $transparency_index);
  2469. imagecolortransparent($dest, $transparency_index);
  2470. }
  2471. else //there is not a transparency index
  2472. {
  2473. //find a color that does not exist in the image
  2474. $found = false;
  2475. $attempts = 0;
  2476. while ($found == false && $attempts < 300)
  2477. {
  2478. $attempts++;
  2479. $r = rand(0, 255);
  2480. $g = rand(0, 255);
  2481. $b = rand(0, 255);
  2482. if ( imagecolorexact( $this->handle, $r, $g, $b ) != -1 )
  2483. {
  2484. $found = true;
  2485. break;
  2486. }
  2487. }
  2488. if ( $attempts < 300 )
  2489. {
  2490. $transparency_index = imagecolorallocatealpha($dest, $r, $g, $b, 127);
  2491. imagefill($dest, 0, 0, $transparency_index);
  2492. imagecolortransparent($dest, $transparency_index);
  2493. }
  2494. imagesavealpha($dest, true);
  2495. imagealphablending($dest, true);
  2496. }
  2497. }
  2498. else //has bg_color
  2499. {
  2500. list($r,$g,$b) = sscanf($this->bg_color, '%2x%2x%2x');
  2501. $fill = imagecolorallocate($dest, $r, $g, $b);
  2502. imagefill($dest, 0, 0, $fill);
  2503. imagealphablending($dest, true);
  2504. imagesavealpha($dest, true);
  2505. }
  2506. }
  2507. else if (($this->image_data_orig['type'] == 3 || strtolower($this->image_data['ext']) == 'png') && $this->bg_color == false) //a png image
  2508. {
  2509. $this->is_transparent = true;
  2510. imagealphablending($dest, false);
  2511. imagesavealpha($dest, true);
  2512. $bg = Ce_image_tools::hex_to_rgb($this->bg_color_default, 'ffffff');
  2513. $transparent = imagecolorallocatealpha($dest, $bg[0], $bg[0], $bg[0], 127);
  2514. if ($this->do_crop == false)
  2515. {
  2516. imagefilledrectangle($dest, 0, 0, $this->width_final, $this->height_final, $transparent);
  2517. }
  2518. else
  2519. {
  2520. imagefilledrectangle($dest, 0, 0, $this->width_desired, $this->height_desired, $transparent);
  2521. }
  2522. }
  2523. else
  2524. {
  2525. $this->is_transparent = false;
  2526. //background fill
  2527. if ($this->bg_color == false)
  2528. {
  2529. //make default fill bg_color white instead of black
  2530. $this->bg_color = $this->bg_color_default;
  2531. }
  2532. //only background fill if the image is less than 2000px by 2000px if memory is > 64 to prevent out of memory error
  2533. if ((!$this->do_crop && $this->width_final < 2000 && $this->height_final < 2000) || ($this->do_crop && $this->width_desired < 2000 && $this->height_desired < 2000) || $this->memory_limit > 64)
  2534. {
  2535. list($r, $g, $b) = sscanf($this->bg_color, '%2x%2x%2x');
  2536. $fill = imagecolorallocate($dest, $r, $g, $b);
  2537. imagefill($dest, 0, 0, $fill);
  2538. }
  2539. }
  2540. //flip
  2541. $width_initial = $this->width_initial;
  2542. $height_initial = $this->height_initial;
  2543. $src_x_offset = $this->src_x_offset;
  2544. $src_y_offset = $this->src_y_offset;
  2545. if ( $this->flip !== false )
  2546. {
  2547. //offsets
  2548. $crop_offset_x = 0;
  2549. $crop_offset_y = 0;
  2550. if ( count( $this->crop ) > 2 )
  2551. {
  2552. $temp = $this->crop[2];
  2553. if ( count( $temp ) == 2 )
  2554. {
  2555. list( $temp_x, $temp_y ) = $temp;
  2556. if ( is_numeric( $temp_x ) )
  2557. {
  2558. $crop_offset_x = $temp_x;
  2559. }
  2560. if ( is_numeric( $temp_y ) )
  2561. {
  2562. $crop_offset_y = $temp_y;
  2563. }
  2564. }
  2565. }
  2566. if ( in_array( 'h', $this->flip ) )
  2567. {
  2568. $src_x_offset = $width_initial + $this->src_x_offset - 1;
  2569. $width_initial = - $width_initial;
  2570. $this->x_offset -= $crop_offset_x * 2;
  2571. }
  2572. if ( in_array( 'v', $this->flip ) )
  2573. {
  2574. $src_y_offset = $height_initial + $this->src_y_offset - 1;
  2575. $height_initial = - $height_initial;
  2576. $this->y_offset -= $crop_offset_y * 2;
  2577. }
  2578. }
  2579. //copy original image to the new image
  2580. if ( @imagecopyresampled( $dest, $this->handle, $this->x_offset, $this->y_offset, $src_x_offset, $src_y_offset, $this->width_final, $this->height_final, $width_initial, $height_initial) == false )
  2581. {
  2582. $this->debug_messages[] = 'Couldn\'t resize image.';
  2583. return false;
  2584. }
  2585. //destroy original image resource
  2586. imagedestroy($this->handle);
  2587. //apply filter(s) if applicable
  2588. if ( is_array( $this->filters ) )
  2589. {
  2590. foreach ( $this->filters as $filter )
  2591. {
  2592. $filter_call = $this->valid_filters[ $filter['0'] ];
  2593. if ( $filter_call === 'imagefilter' )
  2594. {
  2595. $filter['0'] = constant( 'IMG_FILTER_' . strtoupper( $filter['0'] ) );
  2596. array_unshift( $filter, $dest );
  2597. @call_user_func_array( $filter_call, $filter );
  2598. }
  2599. else
  2600. {
  2601. $filter['0'] = $dest; //add the image resource as the first parameter
  2602. $dest = @call_user_func_array( $filter_call, $filter );
  2603. }
  2604. }
  2605. }
  2606. $this->width_final = imagesx( $dest );
  2607. $this->height_final = imagesy( $dest );
  2608. //if this is a png that will be saved to a jpg, turn it opaque so that the watermark can be added in correctly
  2609. if ( $this->image_data_orig['type'] == 3 && $this->bg_color == false && ( strtolower( $this->image_data['ext'] ) == 'jpg' || strtolower( $this->image_data['ext'] ) == 'jpeg' ) ) //png being saved to jpg format
  2610. {
  2611. $this->bg_color = $this->bg_color_default;
  2612. if ( $this->bg_color == false )
  2613. {
  2614. $this->bg_color = 'ffffff';
  2615. }
  2616. $dest = $this->transparent_to_opaque( $dest, $this->width_final, $this->height_final );
  2617. imagealphablending($dest, true);
  2618. imagesavealpha($dest, false);
  2619. $this->is_transparent = false;
  2620. }
  2621. //watermark
  2622. if ( $this->watermark != '' )
  2623. {
  2624. $dest = $this->add_watermark( $dest );
  2625. }
  2626. //text
  2627. if ( $this->text != '' )
  2628. {
  2629. $this->prepare_text();
  2630. array_unshift( $this->text, $dest );
  2631. $dest = call_user_func_array( array( 'self', 'add_text' ) , $this->text );
  2632. }
  2633. //border
  2634. if ( $this->border[0] > 0 )
  2635. {
  2636. $dest = $this->add_border( $dest, $this->width_final, $this->height_final, $this->border[0], $this->border[1] );
  2637. $this->width_final = round( imagesx( $dest ) );
  2638. $this->height_final = round( imagesy( $dest ) );
  2639. }
  2640. //rounded corners
  2641. if ( $this->corners )
  2642. {
  2643. $dest = $this->prep_round_corners( $dest, $this->width_final, $this->height_final, $this->border[0], $this->border[1] );
  2644. }
  2645. //reflection
  2646. if ( $this->reflection )
  2647. {
  2648. $dest = $this->reflect( $dest, $this->width_final, $this->height_final, $this->reflection_array[0], $this->reflection_array[1], $this->reflection_array[2], $this->reflection_array[3] );
  2649. //set the new width and height
  2650. $this->width_final = round( imagesx( $dest ) );
  2651. $this->height_final = round( imagesy( $dest ) );
  2652. }
  2653. //rotate
  2654. if ( $this->rotate !== false )
  2655. {
  2656. //rotate the image
  2657. if ( $this->bg_color != false || strtolower( $this->image_data['ext'] ) == 'jpg' ) //create opaque bg
  2658. {
  2659. $color = Ce_image_tools::hex_to_rgb( $this->bg_color, 'fff' );
  2660. $dest = imagerotate( $dest, $this->rotate, imagecolorallocate($dest, $color[0], $color[1], $color[2]) );
  2661. }
  2662. else //attempt transparency
  2663. {
  2664. $color = Ce_image_tools::hex_to_rgb( 'fff' );
  2665. $dest = imagerotate( $dest, $this->rotate, imagecolorallocatealpha($dest, $color[0], $color[1], $color[2], 127) );
  2666. }
  2667. imagealphablending($dest, false);
  2668. imagesavealpha($dest, true);
  2669. //set the new width and height
  2670. $this->width_final = round( imagesx( $dest ) );
  2671. $this->height_final = round( imagesy( $dest ) );
  2672. }
  2673. //set the final image resource
  2674. $this->handle = $dest;
  2675. return true;
  2676. }
  2677. /**
  2678. * This function allows png images with no background to be converted to be completely opaque before saving to jpg format. It blends the new background color with the semi-transparent pixels for a smooth transition.
  2679. *
  2680. * @param resource $image The image to be maniplulated.
  2681. * @param int $width The image width.
  2682. * @param int $height The image height.
  2683. * @return resource The opaque image.
  2684. */
  2685. private function transparent_to_opaque( $image, $width, $height )
  2686. {
  2687. //setup the background color
  2688. $bg_rgb = Ce_image_tools::hex_to_rgb( $this->bg_color, 'ffffff' );
  2689. //loop through the pixels
  2690. for ( $y = 0; $y < $height; $y++ ) //row
  2691. {
  2692. //loop through the pixels in the row and set their opacity
  2693. for ( $x = 0; $x < $width; $x++ ) //column
  2694. {
  2695. //get the current color info
  2696. $color = imagecolorat($image, $x, $y);
  2697. $r = $color >> 16 & 0xFF;
  2698. $g = $color >> 8 & 0xFF;
  2699. $b = $color & 0xFF;
  2700. $a = $color >> 24 & 0xFF;
  2701. //no transparency support, blend foreground and background colors using custom solution
  2702. $alpha_inverse = $a / 127;
  2703. $alpha = 1 - $alpha_inverse;
  2704. $r = round( $r * $alpha + $bg_rgb[0] * $alpha_inverse );
  2705. $g = round( $g * $alpha + $bg_rgb[1] * $alpha_inverse );
  2706. $b = round( $b * $alpha + $bg_rgb[2] * $alpha_inverse );
  2707. $color_new = imagecolorallocatealpha($image, $r, $g, $b, 0);
  2708. imagesetpixel($image, $x, $y, $color_new);
  2709. }
  2710. }
  2711. return $image;
  2712. }
  2713. /**
  2714. * Sets the memory limit to the specified limit, if it is less than the default limit (set in .htaccess or php.ini)
  2715. */
  2716. private function set_memory_limit()
  2717. {
  2718. //get the memory_limit
  2719. $default_memory_limit = ini_get( 'memory_limit' );
  2720. if ( empty( $default_memory_limit ) ) //the default memory_limit setting could not be retrieved
  2721. {
  2722. $default_memory_limit = 0;
  2723. }
  2724. //convert the default to megabytes
  2725. $default_memory_limit = Ce_image_tools::return_bytes( $default_memory_limit, 'M' );
  2726. //get the CE Image defined memory limit (which should be an int in megabytes)
  2727. $this->memory_limit = ( ! empty( $this->memory_limit ) && is_numeric( $this->memory_limit ) ) ? (int) $this->memory_limit : 64;
  2728. if ( $this->memory_limit > $default_memory_limit ) //the CE Image memory_limit setting is greater than the current memory_limit
  2729. {
  2730. //set the new limit
  2731. @ini_set( 'memory_limit', $this->memory_limit . 'M' );
  2732. }
  2733. else if ($this->memory_limit < $default_memory_limit)
  2734. {
  2735. $this->memory_limit = $default_memory_limit;
  2736. }
  2737. }
  2738. /**
  2739. * Adds a border to the image.
  2740. *
  2741. * @param resource $old The current image.
  2742. * @param int $width_old The current image width.
  2743. * @param int $height_old The current image height.
  2744. * @param int $border_width The border width.
  2745. * @param string $border_color The border color. Should be a hexadecimal number (with or without the '#').
  2746. * @return resource
  2747. */
  2748. private function add_border( $old, $width_old, $height_old, $border_width = 10, $border_color = 'ffffff' )
  2749. {
  2750. $border_width = round( $border_width );
  2751. //border color
  2752. $width = round( $width_old + $border_width * 2 );
  2753. $height = round( $height_old + $border_width * 2);
  2754. $new = $this->prep_transparency( $old, $width, $height, false );
  2755. $color = Ce_image_tools::hex_to_rgb( $border_color, 'fff' );
  2756. $border_color = imagecolorallocate($new, $color[0], $color[1], $color[2]);
  2757. //top border
  2758. imagefilledrectangle( $new, 0, 0, $width, $border_width, $border_color );
  2759. //right border
  2760. imagefilledrectangle( $new, $width_old + $border_width, $border_width, $width, $height_old + $border_width, $border_color );
  2761. //bottom border
  2762. imagefilledrectangle( $new, 0, $height_old + $border_width, $width, $height, $border_color );
  2763. //left border
  2764. imagefilledrectangle( $new, 0, $border_width, $border_width, $height_old + $border_width, $border_color );
  2765. //copy image over top
  2766. imagecopyresized( $new, $old, $border_width, $border_width, 0, 0, $width_old, $height_old, $width_old, $height_old );
  2767. //free up memory
  2768. imagedestroy( $old );
  2769. return $new;
  2770. }
  2771. /**
  2772. * Adds the watermark to the image.
  2773. *
  2774. * @param resource $image The image resource to add the watermark to.
  2775. * @return resource The image resource with the watermark added.
  2776. */
  2777. private function add_watermark( $image )
  2778. {
  2779. $wm_length = count( $this->wm_array );
  2780. //opacity
  2781. $wm_opacity = ( $wm_length > 2 ) ? trim($this->wm_array[2]) : 100;
  2782. if ($wm_opacity > 100 )
  2783. {
  2784. $wm_opacity = 100;
  2785. }
  2786. else if ($wm_opacity < 0)
  2787. {
  2788. $wm_opacity = 0;
  2789. }
  2790. //position
  2791. $wm_position = ( $wm_length > 3 ) ? $this->wm_array[3] : '';
  2792. $wm_pos_x = 'center';
  2793. $wm_pos_y = 'center';
  2794. if ( $wm_position != 'repeat' )
  2795. {
  2796. //get user defined position
  2797. if ( is_array( $wm_position ) && count( $wm_position ) == 2 )
  2798. {
  2799. if ( in_array( $wm_position[0], $this->x_options ) )
  2800. {
  2801. $wm_pos_x = $wm_position[0];
  2802. }
  2803. if ( in_array( $wm_position[1], $this->y_options ) )
  2804. {
  2805. $wm_pos_y = $wm_position[1];
  2806. }
  2807. }
  2808. }
  2809. else
  2810. {
  2811. $wm_pos_x = 0;
  2812. $wm_pos_y = 0;
  2813. }
  2814. //offset
  2815. $wm_offset = ( $wm_length > 4 ) ? $this->wm_array[4] : '';
  2816. $wm_offset_x = 0;
  2817. $wm_offset_y = 0;
  2818. //get user defined offsets
  2819. if ( is_array( $wm_offset ) )
  2820. {
  2821. if ( count( $wm_offset ) == 2 )
  2822. {
  2823. if ( is_numeric( trim($wm_offset[0]) ) )
  2824. {
  2825. $wm_offset_x = trim($wm_offset[0]);
  2826. }
  2827. if ( is_numeric( trim($wm_offset[1]) ) )
  2828. {
  2829. $wm_offset_y = trim($wm_offset[1]);
  2830. }
  2831. }
  2832. }
  2833. //create watermark image handle
  2834. switch ( $this->wm_image_data['type'] )
  2835. {
  2836. case IMAGETYPE_GIF:
  2837. $wm_image = imagecreatefromgif( $this->wm_image_data['src'] );
  2838. break;
  2839. case IMAGETYPE_PNG:
  2840. $wm_image = imagecreatefrompng( $this->wm_image_data['src'] );
  2841. break;
  2842. case IMAGETYPE_JPEG:
  2843. $wm_image = imagecreatefromjpeg( $this->wm_image_data['src'] );
  2844. break;
  2845. default:
  2846. $this->debug_messages[] = 'Unknown image format for watermark creation.';
  2847. return false;
  2848. }
  2849. $final_x = 0; //default to top
  2850. $final_y = 0; //default to left
  2851. //the repeat offset
  2852. $repeat_offset = false;
  2853. //figure out if the background image should repeat
  2854. if ( $wm_position == 'repeat' )
  2855. {
  2856. $repeat_offset = 50;
  2857. }
  2858. else if (isset($wm_position[0]) && $wm_position[0] == 'repeat') //there is a repeat
  2859. {
  2860. if (isset($wm_position[1]) && is_numeric($wm_position[1]) && $wm_position[1] >= 0 && $wm_position[1] <= 100) //there is a valid repeat offset
  2861. {
  2862. $repeat_offset = $wm_position[1];
  2863. }
  2864. else //default repeat
  2865. {
  2866. $repeat_offset = 50;
  2867. }
  2868. }
  2869. //figure out logo position
  2870. if ( $repeat_offset === false )
  2871. {
  2872. switch ( $wm_pos_x )
  2873. {
  2874. case 'right':
  2875. $final_x = $this->width_final - $this->wm_image_data['width'];
  2876. break;
  2877. case 'left':
  2878. $final_x = 0;
  2879. break;
  2880. default: //center
  2881. $final_x = ($this->width_final - $this->wm_image_data['width']) * .5;
  2882. break;
  2883. }
  2884. switch ( $wm_pos_y )
  2885. {
  2886. case 'bottom':
  2887. $final_y = $this->height_final - $this->wm_image_data['height'];
  2888. break;
  2889. case 'top':
  2890. $final_y = 0;
  2891. break;
  2892. default: //center
  2893. $final_y = ($this->height_final - $this->wm_image_data['height']) * .5;
  2894. break;
  2895. }
  2896. }
  2897. $blend = 'none';
  2898. if ( isset( $this->wm_array[5] ) && in_array( $this->wm_array[5], array( 'multiply' ) ) )
  2899. {
  2900. $blend = $this->wm_array[5];
  2901. }
  2902. $final_x += $wm_offset_x;
  2903. $final_y += $wm_offset_y;
  2904. $is_png = ( strtolower( $this->image_data['ext'] ) == 'png' && ! $this->bg_color );
  2905. if ( $this->is_transparent || $blend != 'none' )
  2906. {
  2907. //set the watermark opacity here
  2908. $wm_image = $this->opacity( $wm_image, $wm_opacity );
  2909. }
  2910. if ( $repeat_offset !== false ) //if repeat is specified, repeat away...
  2911. {
  2912. //calculate the number of rows and columns
  2913. $columns = ceil($this->width_final / ( $this->wm_image_data['width'] + $wm_offset_x));
  2914. $columns++;
  2915. $rows = ceil($this->height_final / ( $this->wm_image_data['height'] + $wm_offset_y));
  2916. for ( $r = 0; $r < $rows; $r++ )
  2917. {
  2918. for ( $c = 0; $c < $columns; $c++ )
  2919. {
  2920. $pos_x = ($this->wm_image_data['width'] + $wm_offset_x) * $c + $wm_offset_x;
  2921. $remainder = $r * $repeat_offset * .01;
  2922. $remainder = $remainder - floor( $remainder );
  2923. $pos_x += ($this->wm_image_data['width'] + $wm_offset_x) * - $remainder;
  2924. $pos_y = ($this->wm_image_data['height'] + $wm_offset_y) * ( $r ) + $wm_offset_y;
  2925. if ( $is_png || $blend != 'none' )
  2926. {
  2927. $this->overlay_image( $image, $wm_image, $pos_x, $pos_y, $this->wm_image_data['width'], $this->wm_image_data['height'], $blend );
  2928. }
  2929. else
  2930. {
  2931. $this->imagecopymerge_alpha($image, $wm_image, $pos_x, $pos_y, 0, 0, $this->wm_image_data['width'], $this->wm_image_data['height'], $wm_opacity );
  2932. }
  2933. }
  2934. }
  2935. }
  2936. else //if no repeat, add the logo -- just once!
  2937. {
  2938. if ( $is_png || $blend != 'none' )
  2939. {
  2940. $this->overlay_image($image, $wm_image, $final_x, $final_y, $this->wm_image_data['width'], $this->wm_image_data['height'], $blend );
  2941. }
  2942. else
  2943. {
  2944. $this->imagecopymerge_alpha($image, $wm_image, $final_x, $final_y, 0, 0, $this->wm_image_data['width'], $this->wm_image_data['height'], $wm_opacity );
  2945. }
  2946. }
  2947. //free up memory
  2948. imagedestroy($wm_image);
  2949. return $image;
  2950. }
  2951. /**
  2952. * A function to overlay a png image watermark over a png image, since the built in (faster) PHP methods will not support transparency properly.
  2953. *
  2954. * @param resource $dst_im The destination (main) image.
  2955. * @param resource $src_im The source (watermark) image.
  2956. * @param int $dst_x The x position of the destination image to start the left most pixel of the watermark.
  2957. * @param int $dst_y The y position of the destination image to start the top most pixel of the watermark.
  2958. * @param int $src_w The width of the source (watermark) image.
  2959. * @param int $src_h The height of the source (watermark) image.
  2960. * @param string $blend Can be 'none' or 'multiply'.
  2961. * @return resource The new image.
  2962. */
  2963. private function overlay_image( $dst_im, $src_im, $dst_x, $dst_y, $src_w, $src_h, $blend = 'none' )
  2964. {
  2965. //get the dst image width and height
  2966. $width = imagesx( $dst_im );
  2967. $height = imagesy( $dst_im );
  2968. //loop through the rows
  2969. for ( $y = 0; $y < $src_h; $y++ ) //row
  2970. {
  2971. $dy = $dst_y + $y;
  2972. if ( $dy < 0 || $dy > $height -1 )
  2973. {
  2974. continue;
  2975. }
  2976. //loop through the pixels in the row
  2977. for ( $x = 0; $x < $src_w; $x++ ) //column
  2978. {
  2979. $dx = $dst_x + $x;
  2980. if ( $dx < 0 || $dx > $width - 1 )
  2981. {
  2982. continue;
  2983. }
  2984. //get the current color info for the source image
  2985. $src_color = imagecolorat($src_im, $x, $y);
  2986. $src_r = $src_color >> 16 & 0xFF;
  2987. $src_g = $src_color >> 8 & 0xFF;
  2988. $src_b = $src_color & 0xFF;
  2989. $src_alpha = $src_color >> 24 & 0xFF;
  2990. //get the color info for the destination image
  2991. $dst_color = imagecolorat($dst_im, $dx, $dy);
  2992. $dst_r = $dst_color >> 16 & 0xFF;
  2993. $dst_g = $dst_color >> 8 & 0xFF;
  2994. $dst_b = $dst_color & 0xFF;
  2995. $dst_alpha = $dst_color >> 24 & 0xFF;
  2996. if ( $blend == 'none' )
  2997. {
  2998. if ( $src_alpha == 127 ) //the pixel is completely transparent
  2999. {
  3000. continue;
  3001. }
  3002. if ( $dst_alpha == 127 )
  3003. {
  3004. //since the dst pixel is completely transparent, set to the src pixel
  3005. $color_new = imagecolorallocatealpha($dst_im, $src_r, $src_g, $src_b, $src_alpha);
  3006. imagesetpixel($dst_im, $dx, $dy, $color_new);
  3007. }
  3008. else
  3009. {
  3010. $t = min( $src_alpha, $dst_alpha );
  3011. $src_alpha = 1 - $src_alpha / 127;
  3012. $dst_alpha = 1 - $src_alpha;
  3013. $total_alpha = $src_alpha + $dst_alpha;
  3014. $src_alpha /= $total_alpha;
  3015. $dst_alpha /= $total_alpha;
  3016. $final_r = round( $src_r * $src_alpha + $dst_r * $dst_alpha );
  3017. $final_g = round( $src_g * $src_alpha + $dst_g * $dst_alpha );
  3018. $final_b = round( $src_b * $src_alpha + $dst_b * $dst_alpha );
  3019. $color_new = imagecolorallocatealpha($dst_im, $final_r, $final_g, $final_b, $t );
  3020. imagesetpixel($dst_im, $dx, $dy, $color_new);
  3021. }
  3022. }
  3023. else if ($blend == 'multiply')
  3024. {
  3025. $t = $dst_alpha;
  3026. if ($dst_alpha == 127) //the main image is transparent, just use the watermark
  3027. {
  3028. $final_r = $src_r;
  3029. $final_g = $src_g;
  3030. $final_b = $src_b;
  3031. $t = $src_alpha;
  3032. }
  3033. else //the main image is not transparent
  3034. {
  3035. if ($src_alpha != 0) //there is some opacity for the watermark, so we need to compensate by blending with white
  3036. {
  3037. $src_alpha = 1 - $src_alpha / 127;
  3038. $inverse_alpha = 1 - $src_alpha;
  3039. $src_r = round($src_r * $src_alpha + 255 * $inverse_alpha);
  3040. $src_g = round($src_g * $src_alpha + 255 * $inverse_alpha);
  3041. $src_b = round($src_b * $src_alpha + 255 * $inverse_alpha);
  3042. }
  3043. $final_r = round($src_r * $dst_r / 255);
  3044. $final_g = round($src_g * $dst_g / 255);
  3045. $final_b = round($src_b * $dst_b / 255);
  3046. }
  3047. $color_new = imagecolorallocatealpha($dst_im, $final_r, $final_g, $final_b, $t);
  3048. imagesetpixel($dst_im, $dx, $dy, $color_new);
  3049. }
  3050. }
  3051. }
  3052. return $dst_im;
  3053. }
  3054. /**
  3055. * A workaround to get transparency support to work for watermark images. Does not seem to work, however, if the destination (main) image is transparent.
  3056. *
  3057. * This method is a derivative work of a function originally posted by Sina Salek 09-Aug-2009 08:26: http://www.php.net/manual/en/function.imagecopymerge.php#92787
  3058. * Original function license: http://creativecommons.org/licenses/by/3.0/legalcode
  3059. *
  3060. * @param resource $dst_im The destination (main) image
  3061. * @param resource $src_im The source (watermark) image
  3062. * @param int $dst_x The x destination position
  3063. * @param int $dst_y The y destination position
  3064. * @param int $src_x The x source position
  3065. * @param int $src_y The y source position
  3066. * @param int $src_w The source image width dimension
  3067. * @param int $src_h The source image height dimension
  3068. * @param int $opacity The source (watermark) image opacity. Ranges from 0 to 100.
  3069. * @return void
  3070. */
  3071. private function imagecopymerge_alpha( $dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $opacity )
  3072. {
  3073. //creating a cut resource
  3074. $cut = imagecreatetruecolor($src_w, $src_h);
  3075. //copying that section of the background to the cut
  3076. imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
  3077. //copy the watermark into the cut
  3078. imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
  3079. //place the cut back into the image
  3080. imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $opacity);
  3081. //free up resources
  3082. imagedestroy( $cut );
  3083. }
  3084. /**
  3085. * Prepares the image as needed and rounds its corners
  3086. *
  3087. * @param resource $old The image that will need it
  3088. * @param int $width
  3089. * @param int $height
  3090. * @param int $border_width
  3091. * @param string $border_color
  3092. * @return resource The manipulated image
  3093. */
  3094. private function prep_round_corners( $old, $width, $height, $border_width = 0, $border_color = 'ff0000')
  3095. {
  3096. $new = $this->prep_transparency( $old, $width, $height );
  3097. foreach ( $this->corners_array as $pos => $corner )
  3098. {
  3099. switch ( $pos )
  3100. {
  3101. case 'tl':
  3102. $left = 0;
  3103. $top = 0;
  3104. break;
  3105. case 'tr':
  3106. $left = 1;
  3107. $top = 0;
  3108. break;
  3109. case 'bl':
  3110. $left = 0;
  3111. $top = 1;
  3112. break;
  3113. case 'br':
  3114. $left = 1;
  3115. $top = 1;
  3116. break;
  3117. default:
  3118. break 2;
  3119. }
  3120. //set the radius and color
  3121. $radius = $corner[0];
  3122. $color = $corner[1];
  3123. if ( $radius == 0 )
  3124. {
  3125. continue;
  3126. }
  3127. //check transparency
  3128. $transparency = 0;
  3129. if ( $this->is_transparent && $color == '' ) //transparent
  3130. {
  3131. $transparency = 127;
  3132. }
  3133. //check color
  3134. if ( $color == '' ) //transparent
  3135. {
  3136. $color = $this->bg_color;
  3137. }
  3138. //check radius
  3139. if ( $radius >= $width || $radius >= $height )
  3140. {
  3141. $radius = ( $width >= $height ) ? $height : $width;
  3142. }
  3143. //round the corner
  3144. $this->round_corners( $new, $width, $height, $radius , $color, $transparency, $top, $left, $border_width, $border_color );
  3145. }
  3146. return $new;
  3147. }
  3148. /**
  3149. * Prepares image transparency.
  3150. *
  3151. * @param resource $old The current image resource.
  3152. * @param int $width The image width.
  3153. * @param int $height The image height.
  3154. * @param bool $copy Copy the old image into the new one?
  3155. * @return resource The new, transparency prepared image resource.
  3156. */
  3157. private function prep_transparency( $old, $width, $height, $copy = true )
  3158. {
  3159. $new = imagecreatetruecolor($width, $height);
  3160. if ( $this->is_transparent )
  3161. {
  3162. if ( ($this->image_data_orig['type'] == 3 || $this->image_data_orig['type'] == 1) && strtolower( $this->image_data['ext'] ) == 'gif' )
  3163. {
  3164. //see if there is a transparent color index
  3165. $transparency_index = imagecolortransparent( $old );
  3166. if ( $transparency_index >= 0 ) //there is a transparency index (gif)
  3167. {
  3168. $transparency_color = @imagecolorsforindex( $old, $transparency_index);
  3169. if ( $this->image_data_orig['type'] == 1 ) //gif to gif
  3170. {
  3171. $transparency_index = imagecolorallocate($new, $transparency_color['red'], $transparency_color['green'], $transparency_color['blue']);
  3172. }
  3173. else //png to gif
  3174. {
  3175. $transparency_index = imagecolorallocatealpha($new, $transparency_color['red'], $transparency_color['green'], $transparency_color['blue'], 127);
  3176. }
  3177. imagefill($new, 0, 0, $transparency_index);
  3178. imagecolortransparent($new, $transparency_index);
  3179. }
  3180. else //there is not a transparency index
  3181. {
  3182. //find a color that does not exist in the image
  3183. $found = false;
  3184. while ($found == false)
  3185. {
  3186. $r = rand(0, 255);
  3187. $g = rand(0, 255);
  3188. $b = rand(0, 255);
  3189. if ( imagecolorexact( $old, $r, $g, $b ) != -1 )
  3190. {
  3191. $found = true;
  3192. break;
  3193. }
  3194. }
  3195. $transparency_index = imagecolorallocatealpha($new, $r, $g, $b, 127);
  3196. imagefill($new, 0, 0, $transparency_index);
  3197. imagecolortransparent($new, $transparency_index);
  3198. imagesavealpha($new, true);
  3199. imagealphablending($new, true);
  3200. }
  3201. }
  3202. else
  3203. {
  3204. imagealphablending($new, false);
  3205. imagesavealpha($new, true);
  3206. $transparent = imagecolorallocatealpha($new, 255, 255, 255, 127);
  3207. imagefilledrectangle($new, 0, 0, $width, $height, $transparent);
  3208. }
  3209. }
  3210. else
  3211. {
  3212. imagealphablending($new, true);
  3213. imagesavealpha($new, false);
  3214. }
  3215. if ( $copy === true )
  3216. {
  3217. imagecopy($new, $old, 0, 0, 0, 0, $width, $height);
  3218. imagedestroy($old);
  3219. }
  3220. return $new;
  3221. }
  3222. /**
  3223. * Reflects an image
  3224. *
  3225. * @param resource $old The current image (without the reflection).
  3226. * @param int $width The current image's width
  3227. * @param int $height The current image's height
  3228. * @param int $gap The gap between the image and the reflection
  3229. * @param int $start_opacity The starting opacity of the reflection
  3230. * @param int $end_opacity The ending opacity of the reflection
  3231. * @param int|string $ref_height The desired reflection height. Can be an integer or a % of the original.
  3232. * @return resource The new image resource with the created reflection.
  3233. */
  3234. private function reflect( $old, $width, $height, $gap = 0, $start_opacity = 80, $end_opacity = 0, $ref_height = '50%' )
  3235. {
  3236. //---------- figure out the params ----------
  3237. if ( ! is_numeric( $gap ) )
  3238. {
  3239. $gap = 0;
  3240. }
  3241. //figure out the height
  3242. if ( substr( $ref_height, -1 ) == '%' ) //percentage
  3243. {
  3244. $ref_height = (int) substr( $ref_height, 0, -1 ); //remove the % sign
  3245. if ( ! is_numeric( $ref_height ) )
  3246. {
  3247. $ref_height = 1;
  3248. }
  3249. $ref_height = ceil($height * $ref_height * .01 ); //% height to actual height
  3250. }
  3251. //validate height
  3252. $ref_height = round( $ref_height );
  3253. if ( $ref_height > $height )
  3254. {
  3255. $ref_height = $height;
  3256. }
  3257. else if ($ref_height <= 0) //no reflection, not much more to do
  3258. {
  3259. return $old;
  3260. }
  3261. //start opacity
  3262. if ( ! is_numeric( $start_opacity ) )
  3263. {
  3264. $start_opacity = 80;
  3265. }
  3266. $start_opacity = round(127 * $start_opacity / 100);
  3267. if ( $start_opacity < 0 )
  3268. {
  3269. $start_opacity = 0;
  3270. }
  3271. else if ($start_opacity > 127)
  3272. {
  3273. $start_opacity = 127;
  3274. }
  3275. //end opacity
  3276. $end_opacity = round( 127 * $end_opacity / 100 );
  3277. if ( ! is_numeric( $end_opacity ) )
  3278. {
  3279. $end_opacity = 0;
  3280. }
  3281. if ( $end_opacity < 0 )
  3282. {
  3283. $end_opacity = 0;
  3284. }
  3285. else if ($end_opacity > 127)
  3286. {
  3287. $end_opacity = 127;
  3288. }
  3289. //---------- create the images and the reflection ----------
  3290. //create the final image and a temp image
  3291. $final = $this->prep_transparency( $old, $width, $height + $ref_height + $gap, false);
  3292. $temp = $this->prep_transparency( $old, $width, $ref_height, false);
  3293. //copy the old into the final
  3294. imagecopy( $final, $old, 0, 0, 0, 0, $width, $height );
  3295. //copy the bottom part of the old image into the temp
  3296. imagecopy( $temp, $old, 0, 0, 0, $height - $ref_height, $width, $ref_height );
  3297. //destroy the original image
  3298. imagedestroy( $old );
  3299. $opacity_range = abs( $start_opacity - $end_opacity );
  3300. //setup the background color
  3301. $bg_rgb = Ce_image_tools::hex_to_rgb( $this->bg_color, 'ffffff' );
  3302. $is_png = ( strtolower( $this->image_data['ext'] ) == 'png' && ! $this->bg_color );
  3303. $bg_transparency = ( $is_png ) ? 127 : 0;
  3304. //allocate the background color
  3305. $bg_color = imagecolorallocatealpha($final, $bg_rgb[0], $bg_rgb[1], $bg_rgb[2], $bg_transparency);
  3306. //make the gap transparent
  3307. for ( $y = 0; $y < $gap; $y++ ) //row
  3308. {
  3309. for ( $x = 0; $x < $width; $x++ ) //column
  3310. {
  3311. imagesetpixel($final, $x, $y + $height, $bg_color);
  3312. }
  3313. }
  3314. //add in the reflection line by line
  3315. for ( $y = 0; $y < $ref_height; $y++ ) //row
  3316. {
  3317. //copy temp row to final
  3318. imagecopy( $final, $temp, 0, $y + $height + $gap, 0, $ref_height - $y - 1, $width, 1 );
  3319. //determine opacity
  3320. if ( $start_opacity > $end_opacity )
  3321. {
  3322. $opacity = 127 - ($start_opacity - ($y / $ref_height * $opacity_range));
  3323. }
  3324. else
  3325. {
  3326. $opacity = 127 - ($start_opacity + ($y / $ref_height * $opacity_range));
  3327. }
  3328. //loop through the pixels in the row and set their opacity
  3329. for ( $x = 0; $x < $width; $x++ ) //column
  3330. {
  3331. //get the current color info
  3332. $current_color = imagecolorat($final, $x, $y + $height + $gap);
  3333. $current_r = $current_color >> 16 & 0xFF;
  3334. $current_g = $current_color >> 8 & 0xFF;
  3335. $current_b = $current_color & 0xFF;
  3336. $current_alpha = $current_color >> 24 & 0xFF;
  3337. if ( $is_png )
  3338. {
  3339. $t = 127 * (1 - ( 1 - $current_alpha / 127 ) * ( 1 - $opacity / 127));
  3340. if ( $current_alpha != 127 )
  3341. {
  3342. $color_new = imagecolorallocatealpha($final, $current_r, $current_g, $current_b, $t);
  3343. imagesetpixel($final, $x, $y + $height + $gap, $color_new);
  3344. unset($color_new);
  3345. }
  3346. unset( $t );
  3347. }
  3348. else
  3349. {
  3350. //no transparency support, blend foreground and background colors using custom solution
  3351. $alpha_inverse = $opacity / 127;
  3352. $alpha_level = 1 - $alpha_inverse;
  3353. $final_r = round( $current_r * $alpha_level + $bg_rgb[0] * $alpha_inverse );
  3354. $final_g = round( $current_g * $alpha_level + $bg_rgb[1] * $alpha_inverse );
  3355. $final_b = round( $current_b * $alpha_level + $bg_rgb[2] * $alpha_inverse );
  3356. $color_new = imagecolorallocatealpha($final, $final_r, $final_g, $final_b, 0);
  3357. imagesetpixel($final, $x, $y + $height + $gap, $color_new);
  3358. unset($alpha_inverse);
  3359. unset($alpha_level);
  3360. unset($final_r);
  3361. unset($final_g);
  3362. unset($final_b);
  3363. unset($color_new);
  3364. }
  3365. unset($current_color);
  3366. unset($current_r);
  3367. unset($current_g);
  3368. unset($current_b);
  3369. unset($current_alpha);
  3370. }
  3371. }
  3372. imagedestroy( $temp );
  3373. return $final;
  3374. }
  3375. /**
  3376. * Sets the image opacity manually, pixel by pixel.
  3377. *
  3378. * @param resource $image The current image resource.
  3379. * @param int $value The opacity level to set the new image to. Ranges from 0 to 100.
  3380. * @param bool|string $bg false if the default background should be used, a hexadecimal string otherwise.
  3381. * @return resource The new image resource.
  3382. */
  3383. private function opacity( $image, $value = 100, $bg = false )
  3384. {
  3385. $value = ( is_null( $value ) ) ? 100 : $value ;
  3386. if ( $value == 100 )
  3387. {
  3388. //no need to set the opacity, it's completely opaque already...
  3389. return $image;
  3390. }
  3391. //get the width and height
  3392. $width = imagesx( $image );
  3393. $height = imagesy( $image );
  3394. if ( ! is_numeric( $value ) )
  3395. {
  3396. $value = 100;
  3397. }
  3398. $value = round(127 * $value / 100);
  3399. if ( $value < 0 )
  3400. {
  3401. $value = 0;
  3402. }
  3403. else if ($value > 127)
  3404. {
  3405. $value = 127;
  3406. }
  3407. //reverse the value
  3408. $value = 127 - $value;
  3409. //setup the background color
  3410. if ( $bg == false )
  3411. {
  3412. $bg_rgb = Ce_image_tools::hex_to_rgb( $this->bg_color, 'ffffff' );
  3413. }
  3414. else
  3415. {
  3416. $bg_rgb = Ce_image_tools::hex_to_rgb( $bg, 'ffffff' );
  3417. }
  3418. //---------- prep and create the new image ----------
  3419. //create the final image and a temp image
  3420. $final = $this->prep_transparency( $image, $width, $height, true);
  3421. //add in the reflection line by line
  3422. for ( $y = 0; $y < $height; $y++ ) //row
  3423. {
  3424. //loop through the pixels in the row and set their opacity
  3425. for ( $x = 0; $x < $width; $x++ ) //column
  3426. {
  3427. //get the current color info
  3428. $current_color = imagecolorat($final, $x, $y);
  3429. $current_r = $current_color >> 16 & 0xFF;
  3430. $current_g = $current_color >> 8 & 0xFF;
  3431. $current_b = $current_color & 0xFF;
  3432. $current_alpha = $current_color >> 24 & 0xFF;
  3433. if ( $this->is_transparent )
  3434. {
  3435. $t = 127 * (1 - ( 1 - $current_alpha / 127 ) * ( 1 - $value / 127));
  3436. if ( $current_alpha != 127 )
  3437. {
  3438. $color_new = imagecolorallocatealpha($final, $current_r, $current_g, $current_b, $t);
  3439. imagesetpixel($final, $x, $y, $color_new);
  3440. }
  3441. unset( $t );
  3442. }
  3443. else
  3444. {
  3445. //no transparency support, blend foreground and background colors using custom solution
  3446. $alpha_inverse = $value / 127;
  3447. $alpha_level = 1 - $alpha_inverse;
  3448. $final_r = round( $current_r * $alpha_level + $bg_rgb[0] * $alpha_inverse );
  3449. $final_g = round( $current_g * $alpha_level + $bg_rgb[1] * $alpha_inverse );
  3450. $final_b = round( $current_b * $alpha_level + $bg_rgb[2] * $alpha_inverse );
  3451. $color_new = imagecolorallocatealpha($final, $final_r, $final_g, $final_b, 0);
  3452. imagesetpixel($final, $x, $y, $color_new);
  3453. }
  3454. }
  3455. }
  3456. return $final;
  3457. }
  3458. /**
  3459. * Sepia filter with transparency support.
  3460. *
  3461. * @param resource $image The original image.
  3462. * @return resource $image The manipulated image.
  3463. */
  3464. private function sepia( $image )
  3465. {
  3466. //get the width and height
  3467. $width = imagesx( $image );
  3468. $height = imagesy( $image );
  3469. //add in the reflection line by line
  3470. for ( $y = 0; $y < $height; $y++ ) //row
  3471. {
  3472. //loop through the pixels in the row and set their opacity
  3473. for ( $x = 0; $x < $width; $x++ ) //column
  3474. {
  3475. //get the current color info
  3476. $color = imagecolorat($image, $x, $y);
  3477. $r = $color >> 16 & 0xFF;
  3478. $g = $color >> 8 & 0xFF;
  3479. $b = $color & 0xFF;
  3480. $a = $color >> 24 & 0xFF;
  3481. $r = 0.393 * $r + 0.769 * $g + 0.189 * $b;
  3482. $g = (int) min( $r / 1.125, 255 ); //(int) min( 0.349 * $current_r + 0.686 * $current_g + 0.168 * $current_b, 255 );
  3483. $b = (int) min( $r / 1.44, 255 ); //(int) min( 0.272 * $current_r + 0.534 * $current_g + 0.131 * $current_b, 255 );
  3484. $r = (int) min( $r, 255 );
  3485. $color_new = imagecolorallocatealpha($image, $r, $g, $b, ( $this->is_transparent ) ? $a : 0 );
  3486. imagesetpixel($image, $x, $y, $color_new);
  3487. }
  3488. }
  3489. return $image;
  3490. }
  3491. /**
  3492. * Replace colors.
  3493. *
  3494. * Examples or the rules parameter:
  3495. * $some_gd_image, array( array( '#fffffe-#111', 'f00' ), array( '#f00', '#000' ) )
  3496. * $some_gd_image, '#fffffe-#111', 'f00', '#f00', '#000'
  3497. *
  3498. * @param resource $image The original image
  3499. * @param mixed $rules An array containing arrays of color_range, replacement_color values. If a replacement_color is false, the color will become transparent. Additionally, this function can take comma delimited color_range, replacement_color sets. See the examples in the description above
  3500. * @return resource $image The new image.
  3501. */
  3502. private function replace_colors( $image, $rules = array() )
  3503. {
  3504. //validate the rules array
  3505. if ( is_null( $rules ) )
  3506. {
  3507. return $image;
  3508. }
  3509. else if ( !is_array( $rules) && func_num_args() > 1 )
  3510. {
  3511. //get the function arguments
  3512. $args = func_get_args();
  3513. array_shift( $args );
  3514. $rules = array();
  3515. //loop through the args and turn into an associative array
  3516. foreach ( $args as $index => $arg )
  3517. {
  3518. if ( $index & 1 ) //is the index odd?
  3519. {
  3520. $rules[] = array( $args[$index - 1], $args[$index] );
  3521. }
  3522. }
  3523. }
  3524. if ( count( $rules ) < 1 )
  3525. {
  3526. return $image;
  3527. }
  3528. $regs = array();
  3529. if ( ! is_array( $rules[0] ) )
  3530. {
  3531. $temp = array();
  3532. $temp[0] = $rules;
  3533. $rules = $temp;
  3534. unset( $temp );
  3535. }
  3536. //validate the rules values
  3537. foreach ( $rules as $rule )
  3538. {
  3539. if ( isset( $rule[0] ) )
  3540. {
  3541. $range = $rule[0];
  3542. }
  3543. else
  3544. {
  3545. $this->debug_messages[] = 'No color range was received, so the rule is being skipped.';
  3546. continue;
  3547. }
  3548. if ( isset( $rule[1] ) )
  3549. {
  3550. $color = $rule[1];
  3551. }
  3552. else
  3553. {
  3554. $color = false;
  3555. }
  3556. $range = explode( '-', $range );
  3557. $data = array();
  3558. //range start value
  3559. if ( isset( $range[0] ) )
  3560. {
  3561. $range[0] = Ce_image_tools::hex_cleanup( $range[0] );
  3562. if ( $range[0] === false ) //the hex is not valid, continue
  3563. {
  3564. $this->debug_messages[] = 'The replace color start range was invalid, so the rule is being skipped.';
  3565. continue;
  3566. }
  3567. else
  3568. {
  3569. $data['start'] = hexdec( $range[0] );
  3570. }
  3571. }
  3572. else //no color is set
  3573. {
  3574. continue;
  3575. }
  3576. //range end value
  3577. if ( isset( $range[1] ) && $range[1] != false )
  3578. {
  3579. $range[1] = Ce_image_tools::hex_cleanup( $range[1] );
  3580. if ( $range[1] === false ) //the hex is not valid, set to start
  3581. {
  3582. $this->debug_messages[] = 'The replace color end range was invalid, so the rule is being skipped.';
  3583. continue;
  3584. }
  3585. else
  3586. {
  3587. $data['end'] = hexdec( $range[1] );
  3588. }
  3589. }
  3590. else
  3591. {
  3592. $data['end'] = hexdec( $range[0] );
  3593. }
  3594. //swap the end and start if needed
  3595. if ( $data['end'] < $data['start'] )
  3596. {
  3597. $temp = $data['end'];
  3598. $data['end'] = $data['start'];
  3599. $data['start'] = $temp;
  3600. }
  3601. //color replace value
  3602. if ( isset( $color ) && $color != false ) //the color value is set
  3603. {
  3604. $color = Ce_image_tools::hex_cleanup( $color );
  3605. if ( $color == false ) //the hex is not valid
  3606. {
  3607. $this->debug_messages[] = 'The replace color was invalid, so the rule is being skipped.';
  3608. continue;
  3609. }
  3610. else //the color is set
  3611. {
  3612. $data['rgb'] = Ce_image_tools::hex_to_rgb( $color );
  3613. $data['color'] = hexdec( $color );
  3614. }
  3615. }
  3616. else //the color equals false
  3617. {
  3618. $data['color'] = false;
  3619. }
  3620. $regs[] = $data;
  3621. }
  3622. unset ( $rules );
  3623. if ( count( $regs ) < 1 )
  3624. {
  3625. return $image;
  3626. }
  3627. //get the width and height
  3628. $width = imagesx( $image );
  3629. $height = imagesy( $image );
  3630. //---------- prep and create the new image ----------
  3631. //create the final image and a temp image
  3632. if ( $this->is_transparent )
  3633. {
  3634. $final = $this->prep_transparency( $image, $width, $height, true);
  3635. }
  3636. else
  3637. {
  3638. $final = $image;
  3639. //background color
  3640. $bg_rgb = Ce_image_tools::hex_to_rgb( $this->bg_color, $this->bg_color_default );
  3641. }
  3642. for ( $y = 0; $y < $height; $y++ ) //row
  3643. {
  3644. //loop through the pixels in the row and set their opacity
  3645. for ( $x = 0; $x < $width; $x++ ) //column
  3646. {
  3647. //get the current color info
  3648. $color = imagecolorat($final, $x, $y);
  3649. $r = $color >> 16 & 0xFF;
  3650. $g = $color >> 8 & 0xFF;
  3651. $b = $color & 0xFF;
  3652. $a = $color >> 24 & 0xFF;
  3653. if ( $a == 127 )
  3654. {
  3655. continue;
  3656. }
  3657. $hex = hexdec( Ce_image_tools::rgb_to_hex( $r, $g, $b ) );
  3658. foreach ( $regs as $reg ) //loop through all the rule sets and see if any match
  3659. {
  3660. if ( $hex >= $reg['start'] && $hex <= $reg['end'] ) //replace the color
  3661. {
  3662. if ( $reg['color'] === false ) //no color
  3663. {
  3664. if ( $this->is_transparent ) //keep the color, but make it transparent
  3665. {
  3666. $a = 127;
  3667. imagesetpixel($final, $x, $y, imagecolorallocatealpha($final, $r, $g, $b, $a ) );
  3668. }
  3669. else //use the background color
  3670. {
  3671. $r = $bg_rgb[0];
  3672. $g = $bg_rgb[1];
  3673. $b = $bg_rgb[2];
  3674. imagesetpixel($final, $x, $y, imagecolorallocate($final, $r, $g, $b ) );
  3675. //calculate the new color value
  3676. $hex = hexdec( Ce_image_tools::rgb_to_hex( $r, $g, $b ) );
  3677. }
  3678. }
  3679. else //new color
  3680. {
  3681. if ( $this->is_transparent ) //same transparency
  3682. {
  3683. $r = $reg['rgb'][0];
  3684. $g = $reg['rgb'][1];
  3685. $b = $reg['rgb'][2];
  3686. imagesetpixel($final, $x, $y, imagecolorallocatealpha($final, $r, $g, $b, $a ) );
  3687. }
  3688. else //fake transparency
  3689. {
  3690. $alpha_inverse = $a / 127;
  3691. $alpha_level = 1 - $alpha_inverse;
  3692. $r = round( $reg['rgb'][0] * $alpha_level + $r * $alpha_inverse );
  3693. $g = round( $reg['rgb'][1] * $alpha_level + $g * $alpha_inverse );
  3694. $b = round( $reg['rgb'][2] * $alpha_level + $b * $alpha_inverse );
  3695. $a = 0;
  3696. imagesetpixel($final, $x, $y, imagecolorallocate($final, $r, $g, $b) );
  3697. }
  3698. //calculate the new color value
  3699. $hex = hexdec( Ce_image_tools::rgb_to_hex( $r, $g, $b ) );
  3700. }
  3701. }
  3702. }
  3703. }
  3704. }
  3705. return $final;
  3706. }
  3707. /**
  3708. * Adds text to the image. This method does no validation (should be taken care of before hand by the prepare_text() method) and expects the parameters to be valid.
  3709. *
  3710. * @param mixed $old_image The resource to add the text to.
  3711. * @param string $text The text to add.
  3712. * @param int $font_size The font size, in pixels.
  3713. * @param int|string $line_height The line height. This can be an integer (to specify the pixel value, eg: 15), or a percentage of the font_size (eg: 120%).
  3714. * @param string $font_color The text color. Can be a 3 or 6 digit hexadecimal number (with or without the #).
  3715. * @param string $font_path The full server path to the TrueType font (.ttf) file to use for the font.
  3716. * @param string $text_align The way the text should align. The options are: 'left', 'center', or 'right'
  3717. * @param int $width_adjustment An integer to adjust the width by. By default, the line width for the text will be the manipulated image's width.
  3718. * @param array $position This parameter will position the block of text in relation to the image. This can be specified as a pair of positions in the form of horizontal_position_string,vertical_position_string. The accepted values for the horizontal position string are: 'left', 'center', or 'right'. The accepted values for the vertical position string are: 'top', 'center', or 'bottom'.
  3719. * @param array $offset The offset of the text after it is positioned in the form offset_x,offset_y.
  3720. * @param int $opacity The transparency of the text, ranging from 0 (completely transparent) to 100 (completely opaque).
  3721. * @param string $shadow_color The shadow text color. Can be a 3 or 6 digit hexadecimal number (with or without the #). The default is #000000. If not specified to a valid hexadecimal color value, the shadow text will not be applied.
  3722. * @param mixed $shadow_offset The offset of the shadow text (in pixels), in the form shadow_offset_x,shadow_offset_y. The default shadow offset is 1,1.
  3723. * @param int $shadow_opacity The transparency of the shadow text, ranging from 0 (completely transparent) to 100 (completely opaque). Defaults to 50.
  3724. * @return bool|resource False on failure. An image of the text on success
  3725. */
  3726. private function add_text( $old_image, $text = '', $font_size = 12, $line_height = 15, $font_color = '000', $font_path = '', $text_align = 'center', $width_adjustment = 0, $position = array( 'center, center'), $offset = array( 0, 0 ), $opacity = 100, $shadow_color = '', $shadow_offset = array( 1, 1 ), $shadow_opacity = 100 )
  3727. {
  3728. //convert html entities to their text equivalent
  3729. $text = html_entity_decode( $text, ENT_QUOTES, 'UTF-8' );
  3730. //set the adjusted width
  3731. $width = $this->width_final + $width_adjustment;
  3732. if ( $width <= 0 ) //check the width
  3733. {
  3734. return $old_image;
  3735. }
  3736. if ( $this->is_transparent )
  3737. {
  3738. $image = imagecreatetruecolor( $this->width_final, $this->height_final );
  3739. imagesavealpha( $image, true );
  3740. imagefill( $image, 0, 0, imagecolorallocatealpha( $image, 0, 0, 0, 127 ) );
  3741. }
  3742. else
  3743. {
  3744. $image = $old_image;
  3745. }
  3746. $words = explode( ' ', $text ); //break up the text into words
  3747. $lines[0] = ''; //the lines
  3748. $current = 0; //the current line index
  3749. //separate the words to lines
  3750. foreach ( $words as $index => $word )
  3751. {
  3752. if ( $word == '\n' )
  3753. {
  3754. $current++;
  3755. $lines[ $current ] = '';
  3756. $current++;
  3757. $lines[ $current ] = '';
  3758. continue;
  3759. }
  3760. $temp = ( $index != 0 ) ? ' ' . $word : $word;
  3761. $line_box = imagettfbbox( $font_size, 0, $font_path, $lines[ $current ] . $temp );
  3762. if ( $line_box[2] - $line_box[0] < $width || $index == 0 ) //if the lower right x - the lower left x < image width
  3763. {
  3764. $lines[ $current ] .= $temp; //add the word to the line
  3765. }
  3766. else
  3767. {
  3768. $current++; //increase the line
  3769. $lines[ $current ] = $word; //add the word to the new line
  3770. }
  3771. }
  3772. //prep the color
  3773. $color = Ce_image_tools::hex_to_rgb( $font_color ); //color rgb from color hex
  3774. $alpha = round( 127 - $opacity * .01 * 127 );
  3775. if ( $alpha < 0 )
  3776. {
  3777. $alpha = 0;
  3778. }
  3779. else if ($alpha > 127)
  3780. {
  3781. $alpha = 127;
  3782. }
  3783. $font_color = imagecolorallocatealpha( $image, $color[0], $color[1], $color[2], $alpha ); //allocate the color
  3784. //check the text shadow settings
  3785. if ( $shadow_opacity == 0 )
  3786. {
  3787. $shadow_color = false;
  3788. }
  3789. if ( $shadow_color !== false )
  3790. {
  3791. $shadow_color = Ce_image_tools::hex_to_rgb( $shadow_color ); //color rgb from color hex
  3792. $alpha = round( 127 - $shadow_opacity * .01 * 127 );
  3793. if ( $alpha < 0 )
  3794. {
  3795. $alpha = 0;
  3796. }
  3797. else if ($alpha > 127)
  3798. {
  3799. $alpha = 127;
  3800. }
  3801. $shadow_font_color = imagecolorallocatealpha( $image, $shadow_color[0], $shadow_color[1], $shadow_color[2], $alpha ); //allocate the color
  3802. }
  3803. //determine the final height
  3804. $height = count( $lines ) * $line_height;
  3805. $start_x = 0;
  3806. $start_y = 0;
  3807. //position
  3808. switch( $position[0] ) //horizontal position
  3809. {
  3810. case 'left':
  3811. $start_x = 0;
  3812. break;
  3813. case 'right':
  3814. $start_x = $this->width_final - $width;
  3815. break;
  3816. default:
  3817. $start_x = ($this->width_final - $width) * .5;
  3818. }
  3819. switch( $position[1] ) //vertical position
  3820. {
  3821. case 'top':
  3822. $start_y = 0;
  3823. break;
  3824. case 'bottom':
  3825. $start_y = $this->height_final - $height;
  3826. break;
  3827. default:
  3828. $start_y = ($this->height_final - $height) * .5;
  3829. }
  3830. //offset
  3831. $start_x += $offset[0];
  3832. $start_y += $offset[1];
  3833. //add the lines to the image
  3834. foreach ( $lines as $index => $line )
  3835. {
  3836. $line_box = imagettfbbox( $font_size, 0, $font_path, $line );
  3837. $line_width = abs( $line_box[0] ) + abs( $line_box[4] ); //lower left x + upper right x
  3838. switch ( $text_align )
  3839. {
  3840. case 'left':
  3841. $line_x = 0;
  3842. break;
  3843. case 'right':
  3844. $line_x = round($width) - round($line_width);
  3845. break;
  3846. case 'center':
  3847. default:
  3848. $line_x = round( ( $width - $line_width ) * .5 ); //centers the text
  3849. }
  3850. $line_x += $start_x; //add in the position and offset
  3851. $line_y = ( ( $line_height ) * ( $index + 1) ) + $start_y; //spaces the text vertically, taking into account the positioning and offset
  3852. //add in the text shadow
  3853. if ( $shadow_color !== false )
  3854. {
  3855. imagettftext( $image, $font_size, 0, $line_x + $shadow_offset[0], $line_y + $shadow_offset[1], $shadow_font_color, $font_path, $line );
  3856. }
  3857. //add the text
  3858. imagettftext( $image, $font_size, 0, $line_x, $line_y, $font_color, $font_path, $line );
  3859. }
  3860. if ( $this->is_transparent )
  3861. {
  3862. $this->overlay_image( $old_image, $image, 0, 0, $this->width_final, $this->height_final );
  3863. }
  3864. return $old_image;
  3865. }
  3866. /**
  3867. * Prepares an image for the edgify filter and calls the edgify filter.
  3868. *
  3869. * @param resource $image The image to apply the filter to.
  3870. * @param int $threshold Lower thresholds will show more lines. Range from 0 to 255 (defaults to 40).
  3871. * @param string $fg_color Hexadecimal color value (3 or 6 digits) or ''(transparent).
  3872. * @return resource The image with the filter applied.
  3873. */
  3874. private function prep_edgify( $image, $threshold = 40, $fg_color = null )
  3875. {
  3876. $threshold = ( is_null( $threshold ) ) ? 40 : $threshold ;
  3877. if ( $this->is_transparent && $this->bg_color == false ) //transparent
  3878. {
  3879. $bg_transparency = 127;
  3880. }
  3881. else
  3882. {
  3883. $bg_transparency = 0;
  3884. }
  3885. $bg_color = ( $this->bg_color == false ) ? 'ffffff' : $this->bg_color;
  3886. if ( is_null( $fg_color ) || $fg_color == '' )
  3887. {
  3888. $fg_transparency = 127;
  3889. $fg_color = '000000';
  3890. }
  3891. else
  3892. {
  3893. $fg_transparency = 0;
  3894. }
  3895. $bg_color = Ce_image_tools::hex_cleanup( $bg_color );
  3896. if ( $bg_color == '' )
  3897. {
  3898. $bg_color == 'ffffff';
  3899. }
  3900. $fg_color = Ce_image_tools::hex_cleanup( $fg_color );
  3901. if ( $fg_color == '' )
  3902. {
  3903. $fg_color == '000000';
  3904. }
  3905. return $this->edgify( $image, $threshold, $bg_color, $fg_color, $bg_transparency, $fg_transparency);
  3906. }
  3907. /**
  3908. * Saves the manipulated image to the specified format.
  3909. *
  3910. * @param string $type
  3911. * @return bool Returns true if successful and false otherwise.
  3912. */
  3913. private function save( $type )
  3914. {
  3915. //save to cache folder
  3916. //check if the directory exists
  3917. if ( ! @is_dir( $this->cache_full ) )
  3918. {
  3919. //turn the cache path into an array of directories
  3920. $directories = explode( '/', substr( $this->cache_full, strlen( $this->base ) ) );
  3921. //assign the current variable
  3922. $current = $this->base;
  3923. //start with base, and add each directory and make sure it exists with the proper permissions
  3924. foreach ( $directories as $directory )
  3925. {
  3926. $current .= '/' . $directory;
  3927. //check if the directory exists
  3928. if ( ! @is_dir( $current ) )
  3929. {
  3930. //try to make the directory with full permissions
  3931. if ( ! @mkdir( $current . '/', $this->dir_permissions, true ) )
  3932. {
  3933. $this->debug_messages[] = "Could not create the cache directory '{$current}'.";
  3934. break;
  3935. }
  3936. }
  3937. }
  3938. /*
  3939. //try to make the directory
  3940. if ( ! @mkdir( $this->cache_full, $this->dir_permissions, true ) )
  3941. {
  3942. $this->debug_messages[] = "Could not create the cache directory '$this->cache_full'";
  3943. return false;
  3944. }
  3945. */
  3946. }
  3947. //ensure the directory is writable
  3948. if ( ! @is_writable( $this->cache_full ) )
  3949. {
  3950. $this->debug_messages[] = "Cache directory '$this->cache_full' is not writable.";
  3951. return false;
  3952. }
  3953. //save as desired type
  3954. $type = strtolower( $type );
  3955. switch ( $type )
  3956. {
  3957. case 'gif':
  3958. $result = imagegif( $this->handle, $this->path_final );
  3959. break;
  3960. case 'png':
  3961. //try to losslessly compress the image as much as possible. Higher compression may take a little longer, but this will only need to happen once as the image will be cached.
  3962. if ( version_compare(PHP_VERSION, '5.1.2', '>=') )
  3963. {
  3964. $result = ( version_compare(PHP_VERSION, '5.1.3', '>=') && defined( 'PNG_ALL_FILTERS' ) ) ? imagepng( $this->handle, $this->path_final, 9, PNG_ALL_FILTERS ) : imagepng( $this->handle, $this->path_final, 9 );
  3965. }
  3966. else
  3967. {
  3968. $result = imagepng( $this->handle, $this->path_final );
  3969. }
  3970. break;
  3971. case 'jpg':
  3972. case 'jpeg':
  3973. default:
  3974. $result = imagejpeg( $this->handle, $this->path_final, $this->quality );
  3975. $type = 'jpg';
  3976. break;
  3977. }
  3978. if ( ! $result )
  3979. {
  3980. $this->debug_messages[] = 'There was a problem saving your file to ' . strtolower( $type ) . ' format.';
  3981. return false;
  3982. }
  3983. else
  3984. {
  3985. $this->debug_messages[] = "Image saved to {$this->path_final}.";
  3986. }
  3987. //saved hook
  3988. if ($this->EE->extensions->active_hook('ce_img_saved'))
  3989. {
  3990. $this->EE->extensions->call('ce_img_saved', $this->path_final, $type );
  3991. }
  3992. //attempt to set permission
  3993. if ( ! @chmod( $this->path_final, $this->image_permissions ) )
  3994. {
  3995. $this->debug_messages[] = "File permissions for '$this->path_final' could not be changed to '$this->image_permissions'.";
  3996. }
  3997. return true;
  3998. }
  3999. /**
  4000. * Converts an image to ASCII art
  4001. *
  4002. * @param resource $image The image to generate ASCII art for.
  4003. * @param bool $use_colors Whether or not to colorize the characters.
  4004. * @param array $ascii_characters The array of characters to be used for the ASCII art.
  4005. * @param bool $repeat Whether or not the characters should repeat in consecutive order (true) or be placed depending on the darkness of the pixel (false).
  4006. * @param bool $space_for_trans If the $repeat parameter is set to true, you can set this parameter to determine whether or not a space should be used for transparent pixels.
  4007. * @return string The HTML for the ASCII art.
  4008. */
  4009. private function create_ascii_art( $image, $use_colors = true, $ascii_characters = array('#', '@', '%', '=', '+', '*', ':', '-', '.', '&nbsp;'), $repeat = false, $space_for_trans = false )
  4010. {
  4011. //dimensions
  4012. $width = imagesx( $image );
  4013. $height = imagesy( $image );
  4014. //art
  4015. $num_asciis = count($ascii_characters);
  4016. $size = 1 / $num_asciis;
  4017. //background color
  4018. $bg_rgb = Ce_image_tools::hex_to_rgb( $this->bg_color, $this->bg_color_default );
  4019. $ascii_image = '';
  4020. $index = -1;
  4021. for ( $y = 0; $y < $height; $y++ ) //rows
  4022. {
  4023. $row = '';
  4024. for ( $x = 0; $x < $width; $x++ ) //columns
  4025. {
  4026. //get the color info for the pixel
  4027. //$rgb = @imagecolorat($image, $x, $y);
  4028. $current_color = @imagecolorat($image, $x, $y);
  4029. $r = $current_color >> 16 & 0xFF;
  4030. $g = $current_color >> 8 & 0xFF;
  4031. $b = $current_color & 0xFF;
  4032. $a = $current_color >> 24 & 0xFF;
  4033. //blend foreground and background colors
  4034. $alpha_inverse = $a / 127;
  4035. $alpha_level = 1 - $alpha_inverse;
  4036. $r = round( $r * $alpha_level + $bg_rgb[0] * $alpha_inverse );
  4037. $g = round( $g * $alpha_level + $bg_rgb[1] * $alpha_inverse );
  4038. $b = round( $b * $alpha_level + $bg_rgb[2] * $alpha_inverse );
  4039. if ( $repeat == false )
  4040. {
  4041. //to grayscale
  4042. $gray = ( 0.299 * $r + 0.587 * $g + 0.114 * $b );
  4043. $black_fraction = $gray / 255; //determine amount of black
  4044. //find the best character
  4045. for ($index = 0; $index < $num_asciis; $index++ )
  4046. {
  4047. if ( $black_fraction < ($index + 1) * $size )
  4048. {
  4049. break; //this is the character index
  4050. }
  4051. }
  4052. $index = min( $index, $num_asciis - 1 ); //verify index
  4053. unset( $gray );
  4054. unset( $black_fraction );
  4055. }
  4056. else
  4057. {
  4058. $index++;
  4059. if ( $index >= $num_asciis )
  4060. {
  4061. $index = 0;
  4062. }
  4063. }
  4064. //changed from == 127 to >= 126 because sometimes GD on Ubuntu does not preserve the transparency completely after an image is resized. Weird.
  4065. if ( $repeat && $space_for_trans && $a >= 126 )
  4066. {
  4067. $temp = '&nbsp;';
  4068. }
  4069. else
  4070. {
  4071. $temp = $ascii_characters[$index];
  4072. }
  4073. if ( $use_colors )
  4074. {
  4075. $temp = '<span style="color:' . Ce_image_tools::rgb_to_hex($r,$g,$b) . ';">' . $temp . '</span>';
  4076. }
  4077. unset( $current_color );
  4078. unset( $r );
  4079. unset( $g );
  4080. unset( $b );
  4081. $row .= $temp;
  4082. unset( $temp );
  4083. }
  4084. $ascii_image .= "$row<br />" . PHP_EOL;
  4085. unset( $row );
  4086. }
  4087. //return original image
  4088. return $ascii_image;
  4089. }
  4090. /**
  4091. * Gets the average color for the image.
  4092. * @param resource $old_image
  4093. * @return string The hexadecimal color value of the average color of the generated image.
  4094. */
  4095. private function find_average_color( $old_image )
  4096. {
  4097. $width = imagesx( $old_image );
  4098. $height = imagesy( $old_image );
  4099. $resized = false;
  4100. //size the image down to improve performance
  4101. if ( $width > 100 && $height > 100 )
  4102. {
  4103. //scale image down
  4104. $width_new = 100;
  4105. $height_new = 100;
  4106. $ratio_x = round( $width / $width_new, 2 );
  4107. $ratio_y = round( $height / $height_new, 2);
  4108. if ( $ratio_x < $ratio_y )
  4109. {
  4110. $width_new = round( $width / ( $height / $height_new ));
  4111. }
  4112. else
  4113. {
  4114. $height_new = round( $height / ( $width / $width_new ));
  4115. }
  4116. $image = $this->prep_transparency($old_image, $width_new, $height_new, false);
  4117. if ( @imagecopyresampled( $image, $old_image, 0, 0, 0, 0, $width_new, $height_new, $width, $height ) == false )
  4118. {
  4119. //there was an error resizing, so use the old image
  4120. $image = $old_image;
  4121. }
  4122. else
  4123. {
  4124. //switch image, and width and height
  4125. $resized = true;
  4126. $width = $width_new;
  4127. $height = $height_new;
  4128. }
  4129. }
  4130. else
  4131. {
  4132. $image = $old_image;
  4133. }
  4134. $r = 0;
  4135. $g = 0;
  4136. $b = 0;
  4137. $pixels = 0;
  4138. for( $y = 0; $y < $height; $y++ )
  4139. {
  4140. for( $x = 0; $x < $width; $x++ )
  4141. {
  4142. $rgb = imagecolorat( $image, $x, $y );
  4143. $a = $rgb >> 24 & 0xFF;
  4144. if ( $a != 127 )
  4145. {
  4146. //attempt to account for transparency
  4147. $r += ($rgb >> 16 & 0xFF);
  4148. $g += ($rgb >> 8 & 0xFF);
  4149. $b += ($rgb & 0xFF);
  4150. $pixels++;
  4151. }
  4152. unset( $rgb );
  4153. unset( $a );
  4154. }
  4155. }
  4156. if ( $resized )
  4157. {
  4158. imagedestroy( $image );
  4159. }
  4160. $final = Ce_image_tools::rgb_to_hex( round($r / $pixels), round($g / $pixels), round($b / $pixels) );
  4161. return $final;
  4162. }
  4163. /**
  4164. * Organizes the colors used in your image by frequency of occurrence, and groups them by a threshold.
  4165. *
  4166. * @param resource $old_image The image to find the top colors for.
  4167. * @param int $how_many The maximum number of colors (color groups) to return.
  4168. * @param int $threshold Value from 0 (very low grouping) to 100 (very high grouping).
  4169. * @return array|bool On success, returns an array of results with each result being an array with the following keys: 'color', 'color_count', 'color_percent'. On failure, returns false.
  4170. */
  4171. private function find_top_colors($old_image, $how_many = 5, $threshold = 33)
  4172. {
  4173. if ( $how_many === 0 || ! is_numeric( $how_many) )
  4174. {
  4175. return false;
  4176. }
  4177. $width = imagesx( $old_image );
  4178. $height = imagesy( $old_image );
  4179. $resized = false;
  4180. //size the image down to improve performance
  4181. if ( $width > 100 && $height > 100 )
  4182. {
  4183. //scale image down
  4184. $width_new = 100;
  4185. $height_new = 100;
  4186. $ratio_x = round( $width / $width_new, 2 );
  4187. $ratio_y = round( $height / $height_new, 2);
  4188. if ( $ratio_x < $ratio_y )
  4189. {
  4190. $width_new = round( $width / ( $height / $height_new ));
  4191. }
  4192. else
  4193. {
  4194. $height_new = round( $height / ( $width / $width_new ));
  4195. }
  4196. $image = $this->prep_transparency($old_image, $width_new, $height_new, false);
  4197. if ( @imagecopyresampled( $image, $old_image, 0, 0, 0, 0, $width_new, $height_new, $width, $height ) == false )
  4198. {
  4199. //there was an error resizing, so use the old image
  4200. $image = $old_image;
  4201. }
  4202. else
  4203. {
  4204. //switch image, and width and height
  4205. $resized = true;
  4206. $width = $width_new;
  4207. $height = $height_new;
  4208. }
  4209. }
  4210. else
  4211. {
  4212. $image = $old_image;
  4213. }
  4214. //convert threshold value of 0-100 to 0-255
  4215. $threshold = round( 255 / 100 * $threshold );
  4216. //check threshold
  4217. if ( $threshold < 1)
  4218. {
  4219. $threshold = 1;
  4220. }
  4221. else if ($threshold > 255)
  4222. {
  4223. $threshold = 255;
  4224. }
  4225. $threshold_counts = array();
  4226. $threshold_colors = array();
  4227. $top_colors = array();
  4228. for( $y = 0; $y < $height; $y++ )
  4229. {
  4230. for( $x = 0; $x < $width; $x++ )
  4231. {
  4232. $rgb = imagecolorat( $image, $x, $y );
  4233. $a = $rgb >> 24 & 0xFF;
  4234. if ( $a == 127 )
  4235. {
  4236. continue;
  4237. }
  4238. $r = $rgb >> 16 & 0xFF;
  4239. $g = $rgb >> 8 & 0xFF;
  4240. $b = $rgb & 0xFF;
  4241. $tr = floor( $r / $threshold) * $threshold;
  4242. $tg = floor( $g / $threshold) * $threshold;
  4243. $tb = floor( $b / $threshold) * $threshold;
  4244. //get the color
  4245. $tcolor = Ce_image_tools::rgb_to_hex( round($tr), round($tg), round($tb), false );
  4246. //add the color to the array
  4247. if ( ! isset( $threshold_counts[$tcolor] ) )
  4248. {
  4249. $threshold_counts[$tcolor] = 1;
  4250. $threshold_colors[$tcolor] = array( 'r' => $r, 'g' => $g, 'b' => $b );
  4251. }
  4252. else
  4253. {
  4254. $threshold_counts[$tcolor]++;
  4255. $threshold_colors[$tcolor]['r'] += $r;
  4256. $threshold_colors[$tcolor]['g'] += $g;
  4257. $threshold_colors[$tcolor]['b'] += $b;
  4258. }
  4259. }
  4260. }
  4261. if ( $resized )
  4262. {
  4263. imagedestroy( $image );
  4264. }
  4265. foreach ( $threshold_counts as $average => $count )
  4266. {
  4267. $color = Ce_image_tools::rgb_to_hex( round( $threshold_colors[$average]['r'] / $count ), round( $threshold_colors[$average]['g'] / $count ), round( $threshold_colors[$average]['b'] / $count ) );
  4268. $top_colors[$color] = $count;
  4269. unset( $color );
  4270. }
  4271. unset( $threshold_counts );
  4272. unset( $threshold_colors );
  4273. //sort the keys, get the keys, and limit to the requested number of items
  4274. arsort( $top_colors );
  4275. // array_keys( $top_colors )
  4276. $top_colors = array_slice( $top_colors, 0, $how_many );
  4277. $colors = array();
  4278. $pixels = $width * $height;
  4279. foreach ( $top_colors as $index => $times )
  4280. {
  4281. $colors[] = array( 'color' => $index, 'color_count' => $times, 'color_percent' => round( $times / $pixels * 100, 1) );
  4282. }
  4283. unset( $top_colors );
  4284. unset( $pixels );
  4285. //return the number of items requested
  4286. return $colors;
  4287. }
  4288. /**
  4289. * Colorized emboss filter.
  4290. *
  4291. * @param resource $image
  4292. * @return resource
  4293. */
  4294. private function emboss_color( $image )
  4295. {
  4296. if ( function_exists('imageconvolution') ) //PHP >= 5.1
  4297. {
  4298. imageconvolution( $image, array(
  4299. array( 1, 1, -1 ),
  4300. array( 1, 1, -1 ),
  4301. array( 1, -1, -1 )
  4302. ), 1, 0);
  4303. }
  4304. return $image;
  4305. }
  4306. /**
  4307. * Adds noise.
  4308. *
  4309. * @param resource $image The original image.
  4310. * @param int $level The noise level.
  4311. * @return resource $image The manipulated image.
  4312. */
  4313. private function noise( $image, $level = 30 )
  4314. {
  4315. //get the width and height
  4316. $width = imagesx( $image );
  4317. $height = imagesy( $image );
  4318. for ($x = 0; $x < $width; ++$x)
  4319. {
  4320. for ($y = 0; $y < $height; ++$y)
  4321. {
  4322. if (rand(0, 1))
  4323. {
  4324. //modified color
  4325. $modifier = rand( $level * -1, $level);
  4326. //current color
  4327. $color = imagecolorat( $image, $x, $y );
  4328. $a = ($color >> 24 & 0xFF);
  4329. if ( $a == 127 )
  4330. {
  4331. continue;
  4332. }
  4333. //add noise
  4334. $a += $modifier;
  4335. $r = ($color >> 16 & 0xFF) + $modifier;
  4336. $g = ($color >> 8 & 0xFF) + $modifier;
  4337. $b = ($color & 0xFF) + $modifier;
  4338. //check values
  4339. if ($r > 255) $r = 255;
  4340. if ($r < 0) $r = 0;
  4341. if ($g > 255) $g = 255;
  4342. if ($g < 0) $g = 0;
  4343. if ($b > 255) $b = 255;
  4344. if ($b < 0) $b = 0;
  4345. if ($a > 127) $a = 127;
  4346. if ($a < 0) $a = 0;
  4347. //create and set new color
  4348. $color = imagecolorallocatealpha($image, $r, $g, $b, ( $this->is_transparent ) ? $a : 0);
  4349. imagesetpixel($image, $x, $y, $color);
  4350. }
  4351. }
  4352. }
  4353. return $image;
  4354. }
  4355. /**
  4356. * Scatters the pixels in an image.
  4357. *
  4358. * @param resource $image The original image.
  4359. * @param int $level The scatter level.
  4360. * @return resource $image The manipulated image.
  4361. */
  4362. private function scatter( $image, $level = 4 )
  4363. {
  4364. //get the width and height
  4365. $width = imagesx( $image );
  4366. $height = imagesy( $image );
  4367. //loop through all of the pixels
  4368. for ($x = 0; $x < $width; ++$x)
  4369. {
  4370. for ($y = 0; $y < $height; ++$y)
  4371. {
  4372. //get the random offsets
  4373. $offset_x = rand( $level * -1, $level);
  4374. $offset_y = rand( $level * -1, $level);
  4375. //make sure we're in bounds
  4376. if ($x + $offset_x >= $width || $x + $offset_x < 0 || $y + $offset_y >= $height || $y + $offset_y < 0)
  4377. {
  4378. continue;
  4379. }
  4380. //swap the pixels
  4381. $color = imagecolorat($image, $x, $y);
  4382. $color_new = imagecolorat($image, $x + $offset_x, $y + $offset_y);
  4383. imagesetpixel($image, $x, $y, $color_new);
  4384. imagesetpixel($image, $x + $offset_x, $y + $offset_y, $color);
  4385. }
  4386. }
  4387. return $image;
  4388. }
  4389. /**
  4390. * Auto-sharpen the image. It's magic. PHP >= 5.1
  4391. *
  4392. * @param resource $image The original image.
  4393. * @return resource $image The manipulated image.
  4394. */
  4395. private function auto_sharpen( $image )
  4396. {
  4397. if ( function_exists('imageconvolution') )
  4398. {
  4399. $width = imagesx( $image );
  4400. $width_orig = $this->image_data_orig['width'];
  4401. //if the image size has not been reduced, then no need to proceed
  4402. if ( $width >= $width_orig )
  4403. {
  4404. return $image;
  4405. }
  4406. $ratio = $width / $width_orig;
  4407. $value = 11 + round( 7 * $ratio );
  4408. $matrix = array(
  4409. array(0, -1, 0),
  4410. array(-1, $value , -1),
  4411. array(0, -1, 0)
  4412. );
  4413. imageconvolution($image, $matrix, array_sum(array_map('array_sum', $matrix)), 0);
  4414. }
  4415. return $image;
  4416. }
  4417. /**
  4418. * A wrapper to easily call the gaussian_blur filter multiple times.
  4419. *
  4420. * @param resource $image The original image.
  4421. * @param int $passes
  4422. * @return resource The manipulated image.
  4423. */
  4424. private function gaussian_blur( $image, $passes = 1 )
  4425. {
  4426. $passes = (int) $passes;
  4427. if ($passes < 1)
  4428. {
  4429. $passes = 1;
  4430. }
  4431. for ($i = 0; $i < $passes; $i++)
  4432. {
  4433. imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR);
  4434. }
  4435. return $image;
  4436. }
  4437. /**
  4438. * A wrapper to easily call the selective_blur filter multiple times.
  4439. *
  4440. * @param resource $image The original image.
  4441. * @param int $passes
  4442. * @return resource The manipulated image.
  4443. */
  4444. private function selective_blur( $image, $passes = 1 )
  4445. {
  4446. $passes = (int) $passes;
  4447. if ($passes < 1)
  4448. {
  4449. $passes = 1;
  4450. }
  4451. for ($i = 0; $i < $passes; $i++)
  4452. {
  4453. imagefilter($image, IMG_FILTER_SELECTIVE_BLUR);
  4454. }
  4455. return $image;
  4456. }
  4457. /**
  4458. * Dot image filter.
  4459. *
  4460. * @param resource $image The original image.
  4461. * @param int $block The block size.
  4462. * @param string $color Optional hexadecimal color. If left blank, the image will be created in color.
  4463. * @param string $type The dot type. Can be 'square' or simply 's' for square, or anything else for 'circle'.
  4464. * @param int $multiplier Adjust the dot size ratio.
  4465. * @return resource $image The manipulated image.
  4466. */
  4467. private function dot( $image, $block = 6, $color = '', $type = 'circle', $multiplier = 1 )
  4468. {
  4469. $type = strtolower( $type );
  4470. if ( $type == 'square' )
  4471. {
  4472. $type = 's';
  4473. }
  4474. if ( ! isset( $block ) )
  4475. {
  4476. $block = 6;
  4477. }
  4478. $block = (int) $block;
  4479. //make sure the block is valid side
  4480. if ( $block < 2 )
  4481. {
  4482. return $image;
  4483. }
  4484. if ( ! isset( $multiplier ) )
  4485. {
  4486. $multiplier = 1;
  4487. }
  4488. //make sure the multiplier is valid
  4489. if ( $multiplier <= 0 )
  4490. {
  4491. $multiplier = .1;
  4492. }
  4493. //get the width and height
  4494. $width = imagesx( $image );
  4495. $height = imagesy( $image );
  4496. //determine the ratio
  4497. $ratio = $block * 0.00784313725 * $multiplier;
  4498. $block_half = $block * .5;
  4499. //create the new image
  4500. $new_image = imagecreatetruecolor($width, $height);
  4501. imagealphablending($new_image, false);
  4502. imagesavealpha($new_image, true);
  4503. $bg_rgb = Ce_image_tools::hex_to_rgb( $this->bg_color, $this->bg_color_default );
  4504. $background = imagecolorallocatealpha($new_image, $bg_rgb[0], $bg_rgb[1], $bg_rgb[2], ( $this->is_transparent && empty( $this->background_color ) ) ? 127 : 0 );
  4505. imagefilledrectangle($new_image, 0, 0, $width, $height, $background);
  4506. //determine color
  4507. if ( isset( $color ) && $color !== '' )
  4508. {
  4509. $main_color = Ce_image_tools::hex_to_rgb( $color, '000' );
  4510. }
  4511. //there is no previous color to start off
  4512. $current_level = null;
  4513. //loop through the pixels and create the new image
  4514. for ($y = 0; $y < $height + $block; $y += $block)
  4515. {
  4516. for ( $x = 0; $x < $width + $block; $x += $block )
  4517. {
  4518. //save previous current level to the last
  4519. $last_level = $current_level;
  4520. //determine the color and radius of the block
  4521. $r = $g = $b = $a = $count = 0;
  4522. for ($yy = $y; $yy < $y + $block; ++$yy)
  4523. {
  4524. if ( $yy >= $height)
  4525. {
  4526. continue;
  4527. }
  4528. for ($xx = $x; $xx < $x + $block; ++$xx)
  4529. {
  4530. if ( $xx >= $width )
  4531. {
  4532. continue;
  4533. }
  4534. $temp = imagecolorat($image, $xx, $yy);
  4535. $alpha = $temp >> 24 & 0xFF;
  4536. if ( $alpha == 127 )
  4537. {
  4538. continue;
  4539. }
  4540. $count++;
  4541. $r += $temp >> 16 & 0xFF;
  4542. $g += $temp >> 8 & 0xFF;
  4543. $b += $temp & 0xFF;
  4544. $a += $alpha;
  4545. }
  4546. }
  4547. if ( ! $count )
  4548. {
  4549. continue;
  4550. }
  4551. $r = round($r /= $count);
  4552. $g = round($g /= $count);
  4553. $b = round($b /= $count);
  4554. $a = round($a /= $count);
  4555. $new_color = isset( $main_color )
  4556. ? imagecolorallocatealpha($new_image, $main_color[0], $main_color[1], $main_color[2], ( $this->is_transparent ) ? $a : 0 )
  4557. : imagecolorallocatealpha($new_image, $r, $g, $b, ( $this->is_transparent ) ? $a : 0 );
  4558. $current_level = max( round( 255 - ( ( $r + $g + $b ) / 3 ) ), 0);
  4559. $size = ( $x != 0 ) ? ($last_level + $current_level) * .5 * $ratio : $current_level * $ratio;
  4560. //add the dot
  4561. if ( $type == 's' )
  4562. {
  4563. $size *= 2/3; //makes the square and circle dot sizes a little more comparable
  4564. $size_half = $size * .5;
  4565. imagefilledrectangle ($new_image, $x + $block_half - $size_half, $y + $block_half - $size_half, $x + $block_half + $size_half, $y + $block_half + $size_half, $new_color);
  4566. }
  4567. else
  4568. {
  4569. imagefilledellipse($new_image, $x + $block_half, $y + $block_half, $size, $size, $new_color);
  4570. }
  4571. }
  4572. }
  4573. return $new_image;
  4574. }
  4575. /**
  4576. * Pixelate an image
  4577. *
  4578. * @param resource $image
  4579. * @param int $size
  4580. * @param null $advanced
  4581. * @return resource
  4582. */
  4583. private function pixelate( $image, $size = 0, $advanced = null )
  4584. {
  4585. if ( $size == 0 || ! is_numeric( $size ) )
  4586. {
  4587. return $image;
  4588. }
  4589. //get the width and height
  4590. $width = imagesx( $image );
  4591. $height = imagesy( $image );
  4592. $y = 0;
  4593. $x = 0;
  4594. while ( $x < $width ) //move the block horizontally
  4595. {
  4596. while ( $y < $height ) //move the block vertically
  4597. {
  4598. //block data
  4599. $block_pixels = 0;
  4600. $rs = array();
  4601. $gs = array();
  4602. $bs = array();
  4603. $as = array();
  4604. //get all the pixels in the block
  4605. for ( $xi = $x; $xi < $x + $size; $xi++ )
  4606. {
  4607. if ( $xi >= $width )
  4608. {
  4609. continue;
  4610. }
  4611. for ( $yi = $y; $yi < $y + $size; $yi++ )
  4612. {
  4613. if ( $yi >= $height )
  4614. {
  4615. continue;
  4616. }
  4617. $block_pixels += 1;
  4618. $color = imagecolorat( $image, $xi, $yi );
  4619. $trans = $color >> 24 & 0xFF;
  4620. $as[] = $trans;
  4621. if ( $trans != 127 ) //only include the colors if the pixel is not completely transparent
  4622. {
  4623. $rs[] = $color >> 16 & 0xFF;
  4624. $gs[] = $color >> 8 & 0xFF;
  4625. $bs[] = $color & 0xFF;
  4626. }
  4627. }
  4628. }
  4629. //average the color data
  4630. $r = array_sum( $rs ) / count( $rs );
  4631. $g = array_sum( $gs ) / count( $gs );
  4632. $b = array_sum( $bs ) / count( $bs );
  4633. $a = array_sum( $as ) / count( $as );
  4634. //create the new color
  4635. $color_new = imagecolorallocatealpha($image, $r, $g, $b, ( $this->is_transparent ) ? $a : 0 );
  4636. //set the color for each of the pixels
  4637. for ( $xi = $x; $xi < $x + $size; $xi++ ) //get the chunk horizontally
  4638. {
  4639. if ( $xi >= $width )
  4640. {
  4641. continue;
  4642. }
  4643. for ( $yi = $y; $yi < $y + $size; $yi++ ) //get the chunk vertically
  4644. {
  4645. if ( $yi >= $height )
  4646. {
  4647. continue;
  4648. }
  4649. imagesetpixel( $image, $xi, $yi, $color_new );
  4650. }
  4651. }
  4652. $y += $size;
  4653. }
  4654. $y = 0;
  4655. $x += $size;
  4656. }
  4657. return $image;
  4658. }
  4659. // ---------------------------------------------------------------------------- 3rd Party Methods ----------------------------------------------------------------------------
  4660. /**
  4661. * Unsharp Mask for PHP - version 2.1.1
  4662. * Unsharp mask algorithm by Torstein Hønsi 2003-07. thoensi_at_netcom_dot_no.
  4663. * Please leave this notice.
  4664. *
  4665. * This method has some minor formatting and code changes by Aaron Waldon of Causing Effect for use with CE Image.
  4666. *
  4667. * @param resource $img The image to apply the filter to.
  4668. * @param int $amount How much of the effect you want, 100 is 'normal.' (typically 50 to 200, defaults to 80).
  4669. * @param float $radius Radius of the blurring circle of the mask. (typically 0.5 to 1, defaults to .5).
  4670. * @param mixed $threshold The least difference in color values that is allowed between the original and the mask. In practice this means that low-contrast areas of the picture are left unrendered whereas edges are treated normally. This is good for pictures of e.g. skin or blue skies. (typically 0 to 5, defaults to 3).
  4671. * @return resource The sharpened image.
  4672. */
  4673. private function unsharp_mask( $img, $amount = 80, $radius = .5, $threshold = 3 )
  4674. {
  4675. $amount = ( is_null( $amount ) ? 80 : $amount );
  4676. $radius = ( is_null( $radius ) ? .5 : $radius );
  4677. $threshold = ( is_null( $threshold ) ? 3 : $threshold );
  4678. //Attempt to calibrate the parameters to Photoshop:
  4679. if ( $amount > 500 )
  4680. {
  4681. $amount = 500;
  4682. }
  4683. $amount = $amount * 0.016;
  4684. if ( $radius > 50 )
  4685. {
  4686. $radius = 50;
  4687. }
  4688. $radius = $radius * 2;
  4689. if ( $threshold > 255 )
  4690. {
  4691. $threshold = 255;
  4692. }
  4693. $radius = abs( round($radius) ); //Only integers make sense.
  4694. if ( $radius == 0 )
  4695. {
  4696. return $img;
  4697. //imagedestroy($img);
  4698. //break;
  4699. }
  4700. $w = imagesx($img);
  4701. $h = imagesy($img);
  4702. $imgCanvas = imagecreatetruecolor($w, $h);
  4703. $imgBlur = imagecreatetruecolor($w, $h);
  4704. //Gaussian blur matrix
  4705. if ( function_exists('imageconvolution') ) //PHP >= 5.1
  4706. {
  4707. $matrix = array(
  4708. array( 1, 2, 1 ),
  4709. array( 2, 4, 2 ),
  4710. array( 1, 2, 1 )
  4711. );
  4712. imagecopy($imgBlur, $img, 0, 0, 0, 0, $w, $h);
  4713. imageconvolution($imgBlur, $matrix, 16, 0);
  4714. }
  4715. else
  4716. {
  4717. //Move copies of the image around one pixel at the time and merge them with weight
  4718. //according to the matrix. The same matrix is simply repeated for higher radii.
  4719. for ($i = 0; $i < $radius; $i++)
  4720. {
  4721. imagecopy ($imgBlur, $img, 0, 0, 1, 0, $w - 1, $h); //left
  4722. imagecopymerge ($imgBlur, $img, 1, 0, 0, 0, $w, $h, 50); //right
  4723. imagecopymerge ($imgBlur, $img, 0, 0, 0, 0, $w, $h, 50); //center
  4724. imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h);
  4725. imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 33.33333 ); //up
  4726. imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 25); //down
  4727. }
  4728. }
  4729. if ( $threshold > 0 )
  4730. {
  4731. //Calculate the difference between the blurred pixels and the original
  4732. //and set the pixels
  4733. for ( $x = 0; $x < $w-1; $x++ ) //each row
  4734. {
  4735. for ( $y = 0; $y < $h; $y++ ) //each pixel
  4736. {
  4737. $rgbOrig = imagecolorat($img, $x, $y);
  4738. $rOrig = (($rgbOrig >> 16) & 0xFF);
  4739. $gOrig = (($rgbOrig >> 8) & 0xFF);
  4740. $bOrig = ($rgbOrig & 0xFF);
  4741. $rgbBlur = imagecolorat($imgBlur, $x, $y);
  4742. $rBlur = (($rgbBlur >> 16) & 0xFF);
  4743. $gBlur = (($rgbBlur >> 8) & 0xFF);
  4744. $bBlur = ($rgbBlur & 0xFF);
  4745. //When the masked pixels differ less from the original
  4746. //than the threshold specifies, they are set to their original value.
  4747. $rNew = ( abs($rOrig - $rBlur) >= $threshold ) ? max(0, min(255, ($amount * ($rOrig - $rBlur)) + $rOrig)) : $rOrig;
  4748. $gNew = ( abs($gOrig - $gBlur) >= $threshold ) ? max(0, min(255, ($amount * ($gOrig - $gBlur)) + $gOrig)) : $gOrig;
  4749. $bNew = ( abs($bOrig - $bBlur) >= $threshold ) ? max(0, min(255, ($amount * ($bOrig - $bBlur)) + $bOrig)) : $bOrig;
  4750. if ( ($rOrig != $rNew) || ($gOrig != $gNew) || ($bOrig != $bNew) )
  4751. {
  4752. $pixCol = imagecolorallocate($img, $rNew, $gNew, $bNew);
  4753. imagesetpixel($img, $x, $y, $pixCol);
  4754. }
  4755. }
  4756. }
  4757. }
  4758. else
  4759. {
  4760. for ( $x = 0; $x < $w; $x++ ) //each row
  4761. {
  4762. for ( $y = 0; $y < $h; $y++ ) //each pixel
  4763. {
  4764. $rgbOrig = imagecolorat($img, $x, $y);
  4765. $rOrig = (($rgbOrig >> 16) & 0xFF);
  4766. $gOrig = (($rgbOrig >> 8) & 0xFF);
  4767. $bOrig = ($rgbOrig & 0xFF);
  4768. $rgbBlur = imagecolorat($imgBlur, $x, $y);
  4769. $rBlur = (($rgbBlur >> 16) & 0xFF);
  4770. $gBlur = (($rgbBlur >> 8) & 0xFF);
  4771. $bBlur = ($rgbBlur & 0xFF);
  4772. $rNew = ($amount * ($rOrig - $rBlur)) + $rOrig;
  4773. if ( $rNew > 255 )
  4774. {
  4775. $rNew=255;
  4776. }
  4777. else if ($rNew < 0)
  4778. {
  4779. $rNew = 0;
  4780. }
  4781. $gNew = ($amount * ($gOrig - $gBlur)) + $gOrig;
  4782. if ( $gNew > 255 )
  4783. {
  4784. $gNew = 255;
  4785. }
  4786. else if ($gNew < 0)
  4787. {
  4788. $gNew = 0;
  4789. }
  4790. $bNew = ($amount * ($bOrig - $bBlur)) + $bOrig;
  4791. if ( $bNew>255 )
  4792. {
  4793. $bNew=255;
  4794. }
  4795. else if ($bNew < 0)
  4796. {
  4797. $bNew = 0;
  4798. }
  4799. $rgbNew = ($rNew << 16) + ($gNew <<8) + $bNew;
  4800. imagesetpixel($img, $x, $y, $rgbNew);
  4801. }
  4802. }
  4803. }
  4804. imagedestroy($imgCanvas);
  4805. imagedestroy($imgBlur);
  4806. return $img;
  4807. }
  4808. /*
  4809. ---------- MIT Style License applicable to the following 4 methods only: ----------
  4810. Permission is hereby granted, free of charge, to any person obtaining a copy of these
  4811. algorithms (the "Software"), to deal in the Software without restriction, including without
  4812. limitation the rights to use, copy, modify, merge, publish, distribute, distribute with
  4813. modifications, sublicense, and/or sell copies of the Software, and to permit persons to whom
  4814. the Software is furnished to do so, subject to the following conditions:
  4815. This permission notice shall be included in all copies or substantial portions of the Software.
  4816. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
  4817. BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4818. NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
  4819. OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
  4820. OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4821. */
  4822. /**
  4823. * The following method is licensed under an MIT-style license, http://www.exorithm.com/home/show/license
  4824. *
  4825. * Round the corners of an image. Transparency and anti-aliasing are supported.
  4826. *
  4827. * Heavily modified by Aaron Waldon of Causing Effect, to include the ability to round 1 corner at a time and add borders that are anti-aliased on each side
  4828. *
  4829. * @version 0.1
  4830. * @author Contributors at eXorithm, modified by Aaron Waldon (aaron@causingeffect.com)
  4831. * @link http://www.exorithm.com/algorithm/view/round_corners Listing at eXorithm
  4832. * @link http://www.exorithm.com/algorithm/history/round_corners History at eXorithm
  4833. * @license for this function http://www.exorithm.com/home/show/license
  4834. *
  4835. * @param resource $image (GD image)
  4836. * @param int $width The image width.
  4837. * @param int $height The image height.
  4838. * @param int $radius Radius of the rounded corners.
  4839. * @param string $color (hex color code) Color of the background.
  4840. * @param int $transparency Level of transparency. 0 is no transparency, 127 is full transparency.
  4841. * @param int $top
  4842. * @param int $left
  4843. * @param int $border_width The width of the border
  4844. * @param string $border_color Hexadecimal color of the border
  4845. * @return resource GD image
  4846. */
  4847. private function round_corners( $image, $width, $height, $radius, $color, $transparency, $top, $left, $border_width, $border_color )
  4848. {
  4849. $full_color = $this->allocate_color($image, $color, $transparency);
  4850. $border_color = $this->allocate_color($image, $border_color, 0);
  4851. $start_x = $left * ( $width - $radius - $border_width );
  4852. $start_y = $top * ( $height - $radius - $border_width );
  4853. $end_x = $start_x + $radius + $border_width;
  4854. $end_y = $start_y + $radius + $border_width;
  4855. $radius_origin_x = $left * ( $start_x - 1 ) + ( ! $left ) * $end_x;
  4856. $radius_origin_y = $top * ( $start_y - 1 ) + ( ! $top ) * $end_y;
  4857. for ( $x = $start_x; $x < $end_x; $x++ )
  4858. {
  4859. for ( $y = $start_y; $y < $end_y; $y++ )
  4860. {
  4861. $dist = sqrt( pow( $x - $radius_origin_x, 2 ) + pow( $y - $radius_origin_y, 2 ) );
  4862. //anti-alias border inside
  4863. if ( $dist > $radius + 1 && $dist <= $radius + $border_width + 1 )
  4864. {
  4865. imagesetpixel($image, $x, $y, $border_color);
  4866. }
  4867. else if ($dist > $radius && $dist <= $radius + 1)
  4868. {
  4869. $pct = 1 - ($dist - $radius);
  4870. $color2 = $this->antialias_pixel($image, $x, $y, $border_color, $pct);
  4871. //don't set the pixel there was an error with anti-aliasing
  4872. if ($color2 !== false)
  4873. {
  4874. imagesetpixel($image, $x, $y, $color2);
  4875. }
  4876. }
  4877. //anti-alias border outside
  4878. if ( $dist > $radius + $border_width + 1 )
  4879. {
  4880. imagesetpixel($image, $x, $y, $full_color);
  4881. }
  4882. else if ($dist > $radius + $border_width && $dist <= $radius + $border_width + 1)
  4883. {
  4884. $pct = 1 - ($dist - $radius - $border_width);
  4885. $color2 = $this->antialias_pixel($image, $x, $y, $full_color, $pct);
  4886. //don't set the pixel there was an error with anti-aliasing
  4887. if ($color2 !== false)
  4888. {
  4889. imagesetpixel($image, $x, $y, $color2);
  4890. }
  4891. }
  4892. }
  4893. }
  4894. return $image;
  4895. }
  4896. /**
  4897. * The following method is licensed under an MIT-style license, http://www.exorithm.com/home/show/license
  4898. * allocate_color
  4899. *
  4900. * Helper function to allocate a color to an image. Color should be a 6-character hex string.
  4901. *
  4902. * @version 0.2
  4903. * @author Contributors at eXorithm, modified by Aaron Waldon (Causing Effect)
  4904. * @link http://www.exorithm.com/algorithm/view/allocate_color Listing at eXorithm
  4905. * @link http://www.exorithm.com/algorithm/history/allocate_color History at eXorithm
  4906. * @license for this function http://www.exorithm.com/home/show/license
  4907. *
  4908. * @param resource $image (GD image) The image that will have the color allocated to it.
  4909. * @param string $color (hex color code) The color to allocate to the image.
  4910. * @param int $transparency The level of transparency from 0 to 127.
  4911. * @return mixed
  4912. */
  4913. private function allocate_color( $image = null, $color, $transparency)
  4914. {
  4915. $r = hexdec(substr($color, 0, 2));
  4916. $g = hexdec(substr($color, 2, 2));
  4917. $b = hexdec(substr($color, 4, 2));
  4918. if ( $transparency > 127 )
  4919. {
  4920. $transparency = 127;
  4921. }
  4922. if ( $transparency <= 0 )
  4923. {
  4924. return imagecolorallocate($image, $r, $g, $b);
  4925. }
  4926. else
  4927. {
  4928. return imagecolorallocatealpha($image, $r, $g, $b, $transparency);
  4929. }
  4930. }
  4931. /**
  4932. * The following method is licensed under an MIT-style license, http://www.exorithm.com/home/show/license
  4933. * antialias_pixel
  4934. *
  4935. * Helper function to apply a certain weight of a certain color to a pixel in an image. The index of the resulting color is returned.
  4936. *
  4937. * @version 0.1
  4938. * @author Contributors at eXorithm, modified by Aaron Waldon (Causing Effect)
  4939. * @link http://www.exorithm.com/algorithm/view/antialias_pixel Listing at eXorithm
  4940. * @link http://www.exorithm.com/algorithm/history/antialias_pixel History at eXorithm
  4941. * @license for this function http://www.exorithm.com/home/show/license
  4942. *
  4943. * @param resource $image (GD image) The image containing the pixel.
  4944. * @param int $x X-axis position of the pixel.
  4945. * @param int $y Y-axis position of the pixel.
  4946. * @param int $color The index of the color to be applied to the pixel.
  4947. * @param float|int $weight Should be between 0 and 1, higher being more of the original pixel color, and 0.5 being an even mixture.
  4948. * @return mixed
  4949. */
  4950. //Modified by Aaron Waldon at Causing Effect to return false if a pixel is out of bounds
  4951. private function antialias_pixel( $image, $x = 0, $y = 0, $color = 0, $weight = 0.5)
  4952. {
  4953. $c = @imagecolorsforindex( $image, $color );
  4954. $r1 = $c['red'];
  4955. $g1 = $c['green'];
  4956. $b1 = $c['blue'];
  4957. $t1 = $c['alpha'];
  4958. //error suppression if the pixel is out of bounds
  4959. $color2 = @imagecolorat($image, $x, $y);
  4960. if ( $color2 === false )
  4961. {
  4962. return false;
  4963. }
  4964. $c = @imagecolorsforindex($image, $color2);
  4965. $r2 = $c['red'];
  4966. $g2 = $c['green'];
  4967. $b2 = $c['blue'];
  4968. $t2 = $c['alpha'];
  4969. $cweight = $weight + ( $t1 / 127 ) * ( 1 - $weight ) - ( $t2 / 127 ) * ( 1 - $weight );
  4970. $r = round( $r2 * $cweight + $r1 * ( 1 - $cweight ) );
  4971. $g = round( $g2 * $cweight + $g1 * ( 1 - $cweight ) );
  4972. $b = round( $b2 * $cweight + $b1 * ( 1 - $cweight ) );
  4973. $t = round( $t2 * $weight + $t1 * ( 1 - $weight ) );
  4974. return imagecolorallocatealpha( $image, $r, $g, $b, $t );
  4975. }
  4976. /**
  4977. * The following method is licensed under an MIT-style license, http://www.exorithm.com/home/show/license
  4978. * edgify
  4979. *
  4980. * Highlight the edges on an image using the Sobel Technique.
  4981. *
  4982. * Heavy modified by Aaron Waldon of Causing Effect, to include the ability to use different foreground and background colors
  4983. *
  4984. * @version 0.1
  4985. * @author Contributors at eXorithm, modified by Aaron Waldon (Causing Effect)
  4986. * @link http://www.exorithm.com/algorithm/view/edgify Listing at eXorithm
  4987. * @link http://www.exorithm.com/algorithm/history/edgify History at eXorithm
  4988. * @license http://www.exorithm.com/home/show/license
  4989. *
  4990. * @param resource $image (GD image)
  4991. * @param int $threshold The threshold value. Lower is less picky about detecting an edge.
  4992. * @param string $bg_color The hexadecimal background color.
  4993. * @param string $fg_color The hexadecimal foreground color.
  4994. * @param int $bg_transparency The background transparency.
  4995. * @param int $fg_transparency The foreground transparency.
  4996. * @return resource GD image
  4997. */
  4998. private function edgify( $image, $threshold, $bg_color, $fg_color, $bg_transparency, $fg_transparency )
  4999. {
  5000. $bg_color = $this->allocate_color($image, $bg_color, $bg_transparency);
  5001. $fg_color = $this->allocate_color($image, $fg_color, $fg_transparency);
  5002. $height = imagesy($image);
  5003. $width = imagesx($image);
  5004. $new_image = imagecreatetruecolor($width, $height);
  5005. imagealphablending($new_image, false);
  5006. imagesavealpha($new_image, true);
  5007. $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
  5008. imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
  5009. if ( defined( 'IMG_FILTER_GRAYSCALE' ) )
  5010. {
  5011. @imagefilter($image, IMG_FILTER_GRAYSCALE );
  5012. }
  5013. else
  5014. {
  5015. return $image;
  5016. }
  5017. //add edges using sobel technique
  5018. for ($x=0; $x < $width; $x++)
  5019. {
  5020. for ($y=0; $y < $height; $y++)
  5021. {
  5022. $x2 = $x+1;
  5023. $y2 = $y+1;
  5024. if ($x2>=$width)
  5025. {
  5026. $x2 = $x;
  5027. }
  5028. if ($y2>=$height)
  5029. {
  5030. $y2 = $y;
  5031. }
  5032. $p1 = imagecolorat($image,$x,$y2) & 0xFF;
  5033. $p2 = imagecolorat($image,$x2,$y2) & 0xFF;
  5034. $p3 = imagecolorat($image,$x,$y) & 0xFF;
  5035. $p4 = imagecolorat($image,$x2,$y) & 0xFF;
  5036. $h = abs($p1 - $p4);
  5037. $k = abs($p2 - $p3);
  5038. $g = $h + $k;
  5039. if ($g > $threshold)
  5040. {
  5041. imagesetpixel($new_image, $x, $y, $fg_color);
  5042. }
  5043. else
  5044. {
  5045. imagesetpixel($new_image, $x, $y, $bg_color);
  5046. }
  5047. }
  5048. }
  5049. //free up memory
  5050. imagedestroy($image);
  5051. return $new_image;
  5052. }
  5053. } /* End of class */
  5054. /**
  5055. * Ce_image_tools
  5056. *
  5057. * @package CE Image
  5058. * @author Causing Effect, Aaron Waldon
  5059. * @link http://www.causingeffect.com
  5060. * @copyright 2011
  5061. * @license http://www.causingeffect.com/software/php/ce-image/license_agreement Causing Effect Commercial License Agreement
  5062. */
  5063. class Ce_image_tools
  5064. {
  5065. /**
  5066. * Converts a file size from bytes to a human readable format.
  5067. *
  5068. * A method derived from a function originally posted by xelozz -at- gmail.com 18-Feb-2010 10:34 to http://us2.php.net/manual/en/function.memory-get-usage.php#96280
  5069. * Original code licensed under: http://creativecommons.org/licenses/by/3.0/legalcode
  5070. *
  5071. * @static
  5072. * @param int $size Bytes.
  5073. * @param int $precision
  5074. * @return string Human readable file size.
  5075. */
  5076. public static function convert( $size, $precision = 2 )
  5077. {
  5078. $negative = false;
  5079. if ( $size == 0 )
  5080. {
  5081. return '0 b';
  5082. }
  5083. else if ( $size < 0 )
  5084. {
  5085. $negative = true;
  5086. $size = abs( $size );
  5087. }
  5088. $unit = array('b','KiB','MiB','GiB','TiB','PiB');
  5089. $size = @round( $size / pow( 1024, ( $i = floor( log( $size, 1024 ) ) ) ), $precision ) . ' ' . $unit[$i];
  5090. return ( $negative ) ? '-' . $size : $size;
  5091. }
  5092. /**
  5093. * Returns the number of bytes, even for strings that use g, gb, m, mb, k, or kb
  5094. *
  5095. * A method derived from a function originally posted by Ivo Mandalski 15-Nov-2011 01:27 http://us3.php.net/manual/en/function.ini-get.php#106518
  5096. * Original code licensed under: http://creativecommons.org/licenses/by/3.0/legalcode
  5097. *
  5098. * @static
  5099. * @param int:string $value
  5100. * @param string $return_as Can be 'b', 'k', 'm', or 'g'.
  5101. * @return int|string
  5102. */
  5103. public static function return_bytes( $value, $return_as = 'b' )
  5104. {
  5105. //return if empty
  5106. if ( empty( $value ) )
  5107. {
  5108. return 0;
  5109. }
  5110. //trim the value
  5111. $value = trim( $value );
  5112. //match the number and the characters (if applicable)
  5113. preg_match( '#([0-9]+)[\s]*([a-z]+)#i', $value, $matches );
  5114. //get the characters
  5115. $last = '';
  5116. if ( isset( $matches[ 2 ] ) )
  5117. {
  5118. $last = $matches[ 2 ];
  5119. }
  5120. //get the numbers
  5121. if ( isset( $matches[ 1 ] ) )
  5122. {
  5123. $value = (int) $matches[ 1 ];
  5124. }
  5125. //determine the number of bytes
  5126. switch ( strtolower( $last ) )
  5127. {
  5128. case 'g':
  5129. case 'gb':
  5130. $value *= 1024;
  5131. case 'm':
  5132. case 'mb':
  5133. $value *= 1024;
  5134. case 'k':
  5135. case 'kb':
  5136. $value *= 1024;
  5137. }
  5138. //determine the return format
  5139. switch ( strtolower( $return_as ) )
  5140. {
  5141. case 'b':
  5142. return (int) $value;
  5143. case 'm':
  5144. case 'mb':
  5145. return (int) $value / ( 1024 * 1024 );
  5146. break;
  5147. case 'k':
  5148. case 'kb':
  5149. return (int) $value / ( 1024 );
  5150. break;
  5151. case 'g':
  5152. case 'gb':
  5153. return (int) $value / ( 1024 * 1024 * 1024 );
  5154. break;
  5155. }
  5156. return 0;
  5157. }
  5158. /**
  5159. * Recursively implodes an array.
  5160. *
  5161. * A method derived from a function posted by kromped at yahoo dot com 09-Feb-2010 12:29 to http://www.php.net/manual/en/function.implode.php#96100
  5162. * Original code licensed under: http://creativecommons.org/licenses/by/3.0/legalcode
  5163. *
  5164. * @param string $glue
  5165. * @param array $pieces
  5166. * @return string The string of the recursively imploded array.
  5167. */
  5168. public static function recursive_implode( $glue, $pieces )
  5169. {
  5170. $final = array();
  5171. foreach( $pieces as $piece )
  5172. {
  5173. $final[] = ( ! is_array( $piece ) ) ? $piece : Ce_image_tools::recursive_implode( $glue, $piece );
  5174. }
  5175. return implode( $glue, $final );
  5176. }
  5177. /**
  5178. * Cleans up a hex value and converts it to RGB
  5179. *
  5180. * @static
  5181. * @param string $hex Hexadecimal color value.
  5182. * @param string $default_hex Fall-back hexadecimal color value.
  5183. * @return array|bool Returns an array on success with the values for red, green, and blue. Returns false on failure.
  5184. */
  5185. public static function hex_to_rgb( $hex, $default_hex = '' )
  5186. {
  5187. $hex = Ce_image_tools::hex_cleanup( $hex );
  5188. if ( $hex == false )
  5189. {
  5190. if ( $default_hex != '' )
  5191. {
  5192. return Ce_image_tools::hex_to_rgb( $default_hex );
  5193. }
  5194. else
  5195. {
  5196. return false;
  5197. }
  5198. }
  5199. return sscanf($hex, '%2x%2x%2x');
  5200. }
  5201. /**
  5202. * Takes a 3 or 6 digit hex color value, strips off the # (if applicable), and returns a 6 digit hex or ''
  5203. *
  5204. * @static
  5205. * @param string $hex A 3 or 6 digit color value.
  5206. * @return string A 6 digit hex color value or ''.
  5207. */
  5208. public static function hex_cleanup( $hex )
  5209. {
  5210. $hex = ( preg_match('/#?[0-9a-fA-F]{3,6}/', $hex) ) ? $hex : false;
  5211. if ( ! $hex )
  5212. {
  5213. return false;
  5214. }
  5215. if ($hex[0] == '#')
  5216. {
  5217. $hex = substr($hex, 1); //trim off #
  5218. }
  5219. if (strlen($hex) == 3)
  5220. {
  5221. $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
  5222. }
  5223. if (strlen($hex) != 6)
  5224. {
  5225. $hex = false;
  5226. }
  5227. return $hex;
  5228. }
  5229. /**
  5230. * Converts RGB color values to a hexadecimal color format.
  5231. *
  5232. * @static
  5233. * @param int $red The red color value. Ranges from 0 to 255.
  5234. * @param int $green The green color value. Ranges from 0 to 255.
  5235. * @param int $blue The blue color value. Ranges from 0 to 255.
  5236. * @param bool $prepend_hash Whether or not to prepend '#' to the hex color value. Defaults to true.
  5237. * @return string The hexadecimal color.
  5238. */
  5239. public static function rgb_to_hex($red, $green, $blue, $prepend_hash = true )
  5240. {
  5241. return (( $prepend_hash ) ? '#' : '') . str_pad(dechex($red), 2, '0', STR_PAD_LEFT) . str_pad(dechex($green), 2, '0', STR_PAD_LEFT) . str_pad(dechex($blue), 2, '0', STR_PAD_LEFT);
  5242. }
  5243. }
  5244. /* End of class Ce_image_tools */
  5245. /* End of file Ce_image.php */