PageRenderTime 26ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/class-wp-image-editor-imagick.php

https://gitlab.com/haque.mdmanzurul/wp-harpar-carolyn
PHP | 511 lines | 263 code | 72 blank | 176 comment | 51 complexity | 68dd3df0225809e5fd10c41199ab5117 MD5 | raw file
  1. <?php
  2. /**
  3. * WordPress Imagick Image Editor
  4. *
  5. * @package WordPress
  6. * @subpackage Image_Editor
  7. */
  8. /**
  9. * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
  10. *
  11. * @since 3.5.0
  12. * @package WordPress
  13. * @subpackage Image_Editor
  14. * @uses WP_Image_Editor Extends class
  15. */
  16. class WP_Image_Editor_Imagick extends WP_Image_Editor {
  17. protected $image = null; // Imagick Object
  18. public function __destruct() {
  19. if ( $this->image instanceof Imagick ) {
  20. // we don't need the original in memory anymore
  21. $this->image->clear();
  22. $this->image->destroy();
  23. }
  24. }
  25. /**
  26. * Checks to see if current environment supports Imagick.
  27. *
  28. * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
  29. * method can be called statically.
  30. *
  31. * @since 3.5.0
  32. * @access public
  33. *
  34. * @return boolean
  35. */
  36. public static function test( $args = array() ) {
  37. // First, test Imagick's extension and classes.
  38. if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) || ! class_exists( 'ImagickPixel' ) )
  39. return false;
  40. if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
  41. return false;
  42. $required_methods = array(
  43. 'clear',
  44. 'destroy',
  45. 'valid',
  46. 'getimage',
  47. 'writeimage',
  48. 'getimageblob',
  49. 'getimagegeometry',
  50. 'getimageformat',
  51. 'setimageformat',
  52. 'setimagecompression',
  53. 'setimagecompressionquality',
  54. 'setimagepage',
  55. 'scaleimage',
  56. 'cropimage',
  57. 'rotateimage',
  58. 'flipimage',
  59. 'flopimage',
  60. );
  61. // Now, test for deep requirements within Imagick.
  62. if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
  63. return false;
  64. if ( array_diff( $required_methods, get_class_methods( 'Imagick' ) ) )
  65. return false;
  66. return true;
  67. }
  68. /**
  69. * Checks to see if editor supports the mime-type specified.
  70. *
  71. * @since 3.5.0
  72. * @access public
  73. *
  74. * @param string $mime_type
  75. * @return boolean
  76. */
  77. public static function supports_mime_type( $mime_type ) {
  78. $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
  79. if ( ! $imagick_extension )
  80. return false;
  81. // setIteratorIndex is optional unless mime is an animated format.
  82. // Here, we just say no if you are missing it and aren't loading a jpeg.
  83. if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' )
  84. return false;
  85. try {
  86. return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
  87. }
  88. catch ( Exception $e ) {
  89. return false;
  90. }
  91. }
  92. /**
  93. * Loads image from $this->file into new Imagick Object.
  94. *
  95. * @since 3.5.0
  96. * @access protected
  97. *
  98. * @return boolean|WP_Error True if loaded; WP_Error on failure.
  99. */
  100. public function load() {
  101. if ( $this->image instanceof Imagick )
  102. return true;
  103. if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
  104. return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
  105. /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
  106. // Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
  107. @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
  108. try {
  109. $this->image = new Imagick( $this->file );
  110. if( ! $this->image->valid() )
  111. return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
  112. // Select the first frame to handle animated images properly
  113. if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
  114. $this->image->setIteratorIndex(0);
  115. $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
  116. }
  117. catch ( Exception $e ) {
  118. return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
  119. }
  120. $updated_size = $this->update_size();
  121. if ( is_wp_error( $updated_size ) )
  122. return $updated_size;
  123. return true;
  124. }
  125. /**
  126. * Sets Image Compression quality on a 1-100% scale.
  127. *
  128. * @since 3.5.0
  129. * @access public
  130. *
  131. * @param int $quality Compression Quality. Range: [1,100]
  132. * @return boolean|WP_Error True if set successfully; WP_Error on failure.
  133. */
  134. public function set_quality( $quality = null ) {
  135. $quality_result = parent::set_quality( $quality );
  136. if ( is_wp_error( $quality_result ) ) {
  137. return $quality_result;
  138. } else {
  139. $quality = $this->get_quality();
  140. }
  141. try {
  142. if ( 'image/jpeg' == $this->mime_type ) {
  143. $this->image->setImageCompressionQuality( $quality );
  144. $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
  145. }
  146. else {
  147. $this->image->setImageCompressionQuality( $quality );
  148. }
  149. }
  150. catch ( Exception $e ) {
  151. return new WP_Error( 'image_quality_error', $e->getMessage() );
  152. }
  153. return true;
  154. }
  155. /**
  156. * Sets or updates current image size.
  157. *
  158. * @since 3.5.0
  159. * @access protected
  160. *
  161. * @param int $width
  162. * @param int $height
  163. */
  164. protected function update_size( $width = null, $height = null ) {
  165. $size = null;
  166. if ( !$width || !$height ) {
  167. try {
  168. $size = $this->image->getImageGeometry();
  169. }
  170. catch ( Exception $e ) {
  171. return new WP_Error( 'invalid_image', __('Could not read image size'), $this->file );
  172. }
  173. }
  174. if ( ! $width )
  175. $width = $size['width'];
  176. if ( ! $height )
  177. $height = $size['height'];
  178. return parent::update_size( $width, $height );
  179. }
  180. /**
  181. * Resizes current image.
  182. *
  183. * At minimum, either a height or width must be provided.
  184. * If one of the two is set to null, the resize will
  185. * maintain aspect ratio according to the provided dimension.
  186. *
  187. * @since 3.5.0
  188. * @access public
  189. *
  190. * @param int|null $max_w Image width.
  191. * @param int|null $max_h Image height.
  192. * @param boolean $crop
  193. * @return boolean|WP_Error
  194. */
  195. public function resize( $max_w, $max_h, $crop = false ) {
  196. if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
  197. return true;
  198. $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
  199. if ( ! $dims )
  200. return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
  201. list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
  202. if ( $crop ) {
  203. return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
  204. }
  205. try {
  206. /**
  207. * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick.
  208. * $this->image->thumbnailImage( $dst_w, $dst_h );
  209. */
  210. $this->image->scaleImage( $dst_w, $dst_h );
  211. }
  212. catch ( Exception $e ) {
  213. return new WP_Error( 'image_resize_error', $e->getMessage() );
  214. }
  215. return $this->update_size( $dst_w, $dst_h );
  216. }
  217. /**
  218. * Resize multiple images from a single source.
  219. *
  220. * @since 3.5.0
  221. * @access public
  222. *
  223. * @param array $sizes {
  224. * An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
  225. *
  226. * Either a height or width must be provided.
  227. * If one of the two is set to null, the resize will
  228. * maintain aspect ratio according to the provided dimension.
  229. *
  230. * @type array $size {
  231. * @type int ['width'] Optional. Image width.
  232. * @type int ['height'] Optional. Image height.
  233. * @type bool $crop Optional. Whether to crop the image. Default false.
  234. * }
  235. * }
  236. * @return array An array of resized images' metadata by size.
  237. */
  238. public function multi_resize( $sizes ) {
  239. $metadata = array();
  240. $orig_size = $this->size;
  241. $orig_image = $this->image->getImage();
  242. foreach ( $sizes as $size => $size_data ) {
  243. if ( ! $this->image )
  244. $this->image = $orig_image->getImage();
  245. if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
  246. continue;
  247. }
  248. if ( ! isset( $size_data['width'] ) ) {
  249. $size_data['width'] = null;
  250. }
  251. if ( ! isset( $size_data['height'] ) ) {
  252. $size_data['height'] = null;
  253. }
  254. if ( ! isset( $size_data['crop'] ) ) {
  255. $size_data['crop'] = false;
  256. }
  257. $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
  258. if( ! is_wp_error( $resize_result ) ) {
  259. $resized = $this->_save( $this->image );
  260. $this->image->clear();
  261. $this->image->destroy();
  262. $this->image = null;
  263. if ( ! is_wp_error( $resized ) && $resized ) {
  264. unset( $resized['path'] );
  265. $metadata[$size] = $resized;
  266. }
  267. }
  268. $this->size = $orig_size;
  269. }
  270. $this->image = $orig_image;
  271. return $metadata;
  272. }
  273. /**
  274. * Crops Image.
  275. *
  276. * @since 3.5.0
  277. * @access public
  278. *
  279. * @param string|int $src The source file or Attachment ID.
  280. * @param int $src_x The start x position to crop from.
  281. * @param int $src_y The start y position to crop from.
  282. * @param int $src_w The width to crop.
  283. * @param int $src_h The height to crop.
  284. * @param int $dst_w Optional. The destination width.
  285. * @param int $dst_h Optional. The destination height.
  286. * @param boolean $src_abs Optional. If the source crop points are absolute.
  287. * @return boolean|WP_Error
  288. */
  289. public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
  290. if ( $src_abs ) {
  291. $src_w -= $src_x;
  292. $src_h -= $src_y;
  293. }
  294. try {
  295. $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
  296. $this->image->setImagePage( $src_w, $src_h, 0, 0);
  297. if ( $dst_w || $dst_h ) {
  298. // If destination width/height isn't specified, use same as
  299. // width/height from source.
  300. if ( ! $dst_w )
  301. $dst_w = $src_w;
  302. if ( ! $dst_h )
  303. $dst_h = $src_h;
  304. $this->image->scaleImage( $dst_w, $dst_h );
  305. return $this->update_size();
  306. }
  307. }
  308. catch ( Exception $e ) {
  309. return new WP_Error( 'image_crop_error', $e->getMessage() );
  310. }
  311. return $this->update_size();
  312. }
  313. /**
  314. * Rotates current image counter-clockwise by $angle.
  315. *
  316. * @since 3.5.0
  317. * @access public
  318. *
  319. * @param float $angle
  320. * @return boolean|WP_Error
  321. */
  322. public function rotate( $angle ) {
  323. /**
  324. * $angle is 360-$angle because Imagick rotates clockwise
  325. * (GD rotates counter-clockwise)
  326. */
  327. try {
  328. $this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
  329. // Since this changes the dimensions of the image, update the size.
  330. $result = $this->update_size();
  331. if ( is_wp_error( $result ) )
  332. return $result;
  333. $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
  334. }
  335. catch ( Exception $e ) {
  336. return new WP_Error( 'image_rotate_error', $e->getMessage() );
  337. }
  338. return true;
  339. }
  340. /**
  341. * Flips current image.
  342. *
  343. * @since 3.5.0
  344. * @access public
  345. *
  346. * @param boolean $horz Flip along Horizontal Axis
  347. * @param boolean $vert Flip along Vertical Axis
  348. * @returns boolean|WP_Error
  349. */
  350. public function flip( $horz, $vert ) {
  351. try {
  352. if ( $horz )
  353. $this->image->flipImage();
  354. if ( $vert )
  355. $this->image->flopImage();
  356. }
  357. catch ( Exception $e ) {
  358. return new WP_Error( 'image_flip_error', $e->getMessage() );
  359. }
  360. return true;
  361. }
  362. /**
  363. * Saves current image to file.
  364. *
  365. * @since 3.5.0
  366. * @access public
  367. *
  368. * @param string $destfilename
  369. * @param string $mime_type
  370. * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
  371. */
  372. public function save( $destfilename = null, $mime_type = null ) {
  373. $saved = $this->_save( $this->image, $destfilename, $mime_type );
  374. if ( ! is_wp_error( $saved ) ) {
  375. $this->file = $saved['path'];
  376. $this->mime_type = $saved['mime-type'];
  377. try {
  378. $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
  379. }
  380. catch ( Exception $e ) {
  381. return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
  382. }
  383. }
  384. return $saved;
  385. }
  386. protected function _save( $image, $filename = null, $mime_type = null ) {
  387. list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
  388. if ( ! $filename )
  389. $filename = $this->generate_filename( null, null, $extension );
  390. try {
  391. // Store initial Format
  392. $orig_format = $this->image->getImageFormat();
  393. $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
  394. $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
  395. // Reset original Format
  396. $this->image->setImageFormat( $orig_format );
  397. }
  398. catch ( Exception $e ) {
  399. return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
  400. }
  401. // Set correct file permissions
  402. $stat = stat( dirname( $filename ) );
  403. $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
  404. @ chmod( $filename, $perms );
  405. /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
  406. return array(
  407. 'path' => $filename,
  408. 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
  409. 'width' => $this->size['width'],
  410. 'height' => $this->size['height'],
  411. 'mime-type' => $mime_type,
  412. );
  413. }
  414. /**
  415. * Streams current image to browser.
  416. *
  417. * @since 3.5.0
  418. * @access public
  419. *
  420. * @param string $mime_type
  421. * @return boolean|WP_Error
  422. */
  423. public function stream( $mime_type = null ) {
  424. list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
  425. try {
  426. // Temporarily change format for stream
  427. $this->image->setImageFormat( strtoupper( $extension ) );
  428. // Output stream of image content
  429. header( "Content-Type: $mime_type" );
  430. print $this->image->getImageBlob();
  431. // Reset Image to original Format
  432. $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
  433. }
  434. catch ( Exception $e ) {
  435. return new WP_Error( 'image_stream_error', $e->getMessage() );
  436. }
  437. return true;
  438. }
  439. }