PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/Gashler/dp
PHP | 477 lines | 247 code | 69 blank | 161 comment | 47 complexity | 3cf96dde8fd54693949a0640c60df695 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. 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. // Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
  106. @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
  107. try {
  108. $this->image = new Imagick( $this->file );
  109. if( ! $this->image->valid() )
  110. return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
  111. // Select the first frame to handle animated images properly
  112. if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
  113. $this->image->setIteratorIndex(0);
  114. $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
  115. }
  116. catch ( Exception $e ) {
  117. return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
  118. }
  119. $updated_size = $this->update_size();
  120. if ( is_wp_error( $updated_size ) )
  121. return $updated_size;
  122. return $this->set_quality();
  123. }
  124. /**
  125. * Sets Image Compression quality on a 1-100% scale.
  126. *
  127. * @since 3.5.0
  128. * @access public
  129. *
  130. * @param int $quality Compression Quality. Range: [1,100]
  131. * @return boolean|WP_Error
  132. */
  133. public function set_quality( $quality = null ) {
  134. if ( !$quality )
  135. $quality = $this->quality;
  136. try {
  137. if( 'image/jpeg' == $this->mime_type ) {
  138. $this->image->setImageCompressionQuality( apply_filters( 'jpeg_quality', $quality, 'image_resize' ) );
  139. $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
  140. }
  141. else {
  142. $this->image->setImageCompressionQuality( $quality );
  143. }
  144. }
  145. catch ( Exception $e ) {
  146. return new WP_Error( 'image_quality_error', $e->getMessage() );
  147. }
  148. return parent::set_quality( $quality );
  149. }
  150. /**
  151. * Sets or updates current image size.
  152. *
  153. * @since 3.5.0
  154. * @access protected
  155. *
  156. * @param int $width
  157. * @param int $height
  158. */
  159. protected function update_size( $width = null, $height = null ) {
  160. $size = null;
  161. if ( !$width || !$height ) {
  162. try {
  163. $size = $this->image->getImageGeometry();
  164. }
  165. catch ( Exception $e ) {
  166. return new WP_Error( 'invalid_image', __('Could not read image size'), $this->file );
  167. }
  168. }
  169. if ( ! $width )
  170. $width = $size['width'];
  171. if ( ! $height )
  172. $height = $size['height'];
  173. return parent::update_size( $width, $height );
  174. }
  175. /**
  176. * Resizes current image.
  177. *
  178. * @since 3.5.0
  179. * @access public
  180. *
  181. * @param int $max_w
  182. * @param int $max_h
  183. * @param boolean $crop
  184. * @return boolean|WP_Error
  185. */
  186. public function resize( $max_w, $max_h, $crop = false ) {
  187. if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
  188. return true;
  189. $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
  190. if ( ! $dims )
  191. return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
  192. list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
  193. if ( $crop ) {
  194. return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
  195. }
  196. try {
  197. /**
  198. * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick.
  199. * $this->image->thumbnailImage( $dst_w, $dst_h );
  200. */
  201. $this->image->scaleImage( $dst_w, $dst_h );
  202. }
  203. catch ( Exception $e ) {
  204. return new WP_Error( 'image_resize_error', $e->getMessage() );
  205. }
  206. return $this->update_size( $dst_w, $dst_h );
  207. }
  208. /**
  209. * Processes current image and saves to disk
  210. * multiple sizes from single source.
  211. *
  212. * 'width' and 'height' are required.
  213. * 'crop' defaults to false when not provided.
  214. *
  215. * @since 3.5.0
  216. * @access public
  217. *
  218. * @param array $sizes { {'width'=>int, 'height'=>int, ['crop'=>bool]}, ... }
  219. * @return array
  220. */
  221. public function multi_resize( $sizes ) {
  222. $metadata = array();
  223. $orig_size = $this->size;
  224. $orig_image = $this->image->getImage();
  225. foreach ( $sizes as $size => $size_data ) {
  226. if ( ! $this->image )
  227. $this->image = $orig_image->getImage();
  228. if ( ! ( isset( $size_data['width'] ) && isset( $size_data['height'] ) ) )
  229. continue;
  230. if ( ! isset( $size_data['crop'] ) )
  231. $size_data['crop'] = false;
  232. $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
  233. if( ! is_wp_error( $resize_result ) ) {
  234. $resized = $this->_save( $this->image );
  235. $this->image->clear();
  236. $this->image->destroy();
  237. $this->image = null;
  238. if ( ! is_wp_error( $resized ) && $resized ) {
  239. unset( $resized['path'] );
  240. $metadata[$size] = $resized;
  241. }
  242. }
  243. $this->size = $orig_size;
  244. }
  245. $this->image = $orig_image;
  246. return $metadata;
  247. }
  248. /**
  249. * Crops Image.
  250. *
  251. * @since 3.5.0
  252. * @access public
  253. *
  254. * @param string|int $src The source file or Attachment ID.
  255. * @param int $src_x The start x position to crop from.
  256. * @param int $src_y The start y position to crop from.
  257. * @param int $src_w The width to crop.
  258. * @param int $src_h The height to crop.
  259. * @param int $dst_w Optional. The destination width.
  260. * @param int $dst_h Optional. The destination height.
  261. * @param boolean $src_abs Optional. If the source crop points are absolute.
  262. * @return boolean|WP_Error
  263. */
  264. public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
  265. if ( $src_abs ) {
  266. $src_w -= $src_x;
  267. $src_h -= $src_y;
  268. }
  269. try {
  270. $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
  271. $this->image->setImagePage( $src_w, $src_h, 0, 0);
  272. if ( $dst_w || $dst_h ) {
  273. // If destination width/height isn't specified, use same as
  274. // width/height from source.
  275. if ( ! $dst_w )
  276. $dst_w = $src_w;
  277. if ( ! $dst_h )
  278. $dst_h = $src_h;
  279. $this->image->scaleImage( $dst_w, $dst_h );
  280. return $this->update_size();
  281. }
  282. }
  283. catch ( Exception $e ) {
  284. return new WP_Error( 'image_crop_error', $e->getMessage() );
  285. }
  286. return $this->update_size();
  287. }
  288. /**
  289. * Rotates current image counter-clockwise by $angle.
  290. *
  291. * @since 3.5.0
  292. * @access public
  293. *
  294. * @param float $angle
  295. * @return boolean|WP_Error
  296. */
  297. public function rotate( $angle ) {
  298. /**
  299. * $angle is 360-$angle because Imagick rotates clockwise
  300. * (GD rotates counter-clockwise)
  301. */
  302. try {
  303. $this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
  304. }
  305. catch ( Exception $e ) {
  306. return new WP_Error( 'image_rotate_error', $e->getMessage() );
  307. }
  308. return $this->update_size();
  309. }
  310. /**
  311. * Flips current image.
  312. *
  313. * @since 3.5.0
  314. * @access public
  315. *
  316. * @param boolean $horz Flip along Horizontal Axis
  317. * @param boolean $vert Flip along Vertical Axis
  318. * @returns boolean|WP_Error
  319. */
  320. public function flip( $horz, $vert ) {
  321. try {
  322. if ( $horz )
  323. $this->image->flipImage();
  324. if ( $vert )
  325. $this->image->flopImage();
  326. }
  327. catch ( Exception $e ) {
  328. return new WP_Error( 'image_flip_error', $e->getMessage() );
  329. }
  330. return true;
  331. }
  332. /**
  333. * Saves current image to file.
  334. *
  335. * @since 3.5.0
  336. * @access public
  337. *
  338. * @param string $destfilename
  339. * @param string $mime_type
  340. * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
  341. */
  342. public function save( $destfilename = null, $mime_type = null ) {
  343. $saved = $this->_save( $this->image, $destfilename, $mime_type );
  344. if ( ! is_wp_error( $saved ) ) {
  345. $this->file = $saved['path'];
  346. $this->mime_type = $saved['mime-type'];
  347. try {
  348. $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
  349. }
  350. catch ( Exception $e ) {
  351. return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
  352. }
  353. }
  354. return $saved;
  355. }
  356. protected function _save( $image, $filename = null, $mime_type = null ) {
  357. list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
  358. if ( ! $filename )
  359. $filename = $this->generate_filename( null, null, $extension );
  360. try {
  361. // Store initial Format
  362. $orig_format = $this->image->getImageFormat();
  363. $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
  364. $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
  365. // Reset original Format
  366. $this->image->setImageFormat( $orig_format );
  367. }
  368. catch ( Exception $e ) {
  369. return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
  370. }
  371. // Set correct file permissions
  372. $stat = stat( dirname( $filename ) );
  373. $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
  374. @ chmod( $filename, $perms );
  375. return array(
  376. 'path' => $filename,
  377. 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
  378. 'width' => $this->size['width'],
  379. 'height' => $this->size['height'],
  380. 'mime-type' => $mime_type,
  381. );
  382. }
  383. /**
  384. * Streams current image to browser.
  385. *
  386. * @since 3.5.0
  387. * @access public
  388. *
  389. * @param string $mime_type
  390. * @return boolean|WP_Error
  391. */
  392. public function stream( $mime_type = null ) {
  393. list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
  394. try {
  395. // Temporarily change format for stream
  396. $this->image->setImageFormat( strtoupper( $extension ) );
  397. // Output stream of image content
  398. header( "Content-Type: $mime_type" );
  399. print $this->image->getImageBlob();
  400. // Reset Image to original Format
  401. $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
  402. }
  403. catch ( Exception $e ) {
  404. return new WP_Error( 'image_stream_error', $e->getMessage() );
  405. }
  406. return true;
  407. }
  408. }