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

/VnKings Theme/VnKing/wp-includes/class-wp-image-editor-imagick.php

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