class-wp-image-editor-imagick.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  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. *
  13. * @see WP_Image_Editor
  14. */
  15. class WP_Image_Editor_Imagick extends WP_Image_Editor {
  16. /**
  17. * Imagick object.
  18. *
  19. * @var Imagick
  20. */
  21. protected $image;
  22. public function __destruct() {
  23. if ( $this->image instanceof Imagick ) {
  24. // we don't need the original in memory anymore
  25. $this->image->clear();
  26. $this->image->destroy();
  27. }
  28. }
  29. /**
  30. * Checks to see if current environment supports Imagick.
  31. *
  32. * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
  33. * method can be called statically.
  34. *
  35. * @since 3.5.0
  36. *
  37. * @static
  38. *
  39. * @param array $args
  40. * @return bool
  41. */
  42. public static function test( $args = array() ) {
  43. // First, test Imagick's extension and classes.
  44. if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) )
  45. return false;
  46. if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
  47. return false;
  48. $required_methods = array(
  49. 'clear',
  50. 'destroy',
  51. 'valid',
  52. 'getimage',
  53. 'writeimage',
  54. 'getimageblob',
  55. 'getimagegeometry',
  56. 'getimageformat',
  57. 'setimageformat',
  58. 'setimagecompression',
  59. 'setimagecompressionquality',
  60. 'setimagepage',
  61. 'setoption',
  62. 'scaleimage',
  63. 'cropimage',
  64. 'rotateimage',
  65. 'flipimage',
  66. 'flopimage',
  67. 'readimage',
  68. );
  69. // Now, test for deep requirements within Imagick.
  70. if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
  71. return false;
  72. $class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
  73. if ( array_diff( $required_methods, $class_methods ) ) {
  74. return false;
  75. }
  76. // HHVM Imagick does not support loading from URL, so fail to allow fallback to GD.
  77. if ( defined( 'HHVM_VERSION' ) && isset( $args['path'] ) && preg_match( '|^https?://|', $args['path'] ) ) {
  78. return false;
  79. }
  80. return true;
  81. }
  82. /**
  83. * Checks to see if editor supports the mime-type specified.
  84. *
  85. * @since 3.5.0
  86. *
  87. * @static
  88. *
  89. * @param string $mime_type
  90. * @return bool
  91. */
  92. public static function supports_mime_type( $mime_type ) {
  93. $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
  94. if ( ! $imagick_extension )
  95. return false;
  96. // setIteratorIndex is optional unless mime is an animated format.
  97. // Here, we just say no if you are missing it and aren't loading a jpeg.
  98. if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' )
  99. return false;
  100. try {
  101. return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
  102. }
  103. catch ( Exception $e ) {
  104. return false;
  105. }
  106. }
  107. /**
  108. * Loads image from $this->file into new Imagick Object.
  109. *
  110. * @since 3.5.0
  111. *
  112. * @return true|WP_Error True if loaded; WP_Error on failure.
  113. */
  114. public function load() {
  115. if ( $this->image instanceof Imagick )
  116. return true;
  117. if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
  118. return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
  119. /*
  120. * Even though Imagick uses less PHP memory than GD, set higher limit
  121. * for users that have low PHP.ini limits.
  122. */
  123. wp_raise_memory_limit( 'image' );
  124. try {
  125. $this->image = new Imagick();
  126. $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
  127. $filename = $this->file;
  128. if ( 'pdf' == $file_extension ) {
  129. $filename = $this->pdf_setup();
  130. }
  131. // Reading image after Imagick instantiation because `setResolution`
  132. // only applies correctly before the image is read.
  133. $this->image->readImage( $filename );
  134. if ( ! $this->image->valid() )
  135. return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
  136. // Select the first frame to handle animated images properly
  137. if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
  138. $this->image->setIteratorIndex(0);
  139. $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
  140. }
  141. catch ( Exception $e ) {
  142. return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
  143. }
  144. $updated_size = $this->update_size();
  145. if ( is_wp_error( $updated_size ) ) {
  146. return $updated_size;
  147. }
  148. return $this->set_quality();
  149. }
  150. /**
  151. * Sets Image Compression quality on a 1-100% scale.
  152. *
  153. * @since 3.5.0
  154. *
  155. * @param int $quality Compression Quality. Range: [1,100]
  156. * @return true|WP_Error True if set successfully; WP_Error on failure.
  157. */
  158. public function set_quality( $quality = null ) {
  159. $quality_result = parent::set_quality( $quality );
  160. if ( is_wp_error( $quality_result ) ) {
  161. return $quality_result;
  162. } else {
  163. $quality = $this->get_quality();
  164. }
  165. try {
  166. if ( 'image/jpeg' == $this->mime_type ) {
  167. $this->image->setImageCompressionQuality( $quality );
  168. $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
  169. }
  170. else {
  171. $this->image->setImageCompressionQuality( $quality );
  172. }
  173. }
  174. catch ( Exception $e ) {
  175. return new WP_Error( 'image_quality_error', $e->getMessage() );
  176. }
  177. return true;
  178. }
  179. /**
  180. * Sets or updates current image size.
  181. *
  182. * @since 3.5.0
  183. *
  184. * @param int $width
  185. * @param int $height
  186. *
  187. * @return true|WP_Error
  188. */
  189. protected function update_size( $width = null, $height = null ) {
  190. $size = null;
  191. if ( !$width || !$height ) {
  192. try {
  193. $size = $this->image->getImageGeometry();
  194. }
  195. catch ( Exception $e ) {
  196. return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file );
  197. }
  198. }
  199. if ( ! $width )
  200. $width = $size['width'];
  201. if ( ! $height )
  202. $height = $size['height'];
  203. return parent::update_size( $width, $height );
  204. }
  205. /**
  206. * Resizes current image.
  207. *
  208. * At minimum, either a height or width must be provided.
  209. * If one of the two is set to null, the resize will
  210. * maintain aspect ratio according to the provided dimension.
  211. *
  212. * @since 3.5.0
  213. *
  214. * @param int|null $max_w Image width.
  215. * @param int|null $max_h Image height.
  216. * @param bool $crop
  217. * @return bool|WP_Error
  218. */
  219. public function resize( $max_w, $max_h, $crop = false ) {
  220. if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
  221. return true;
  222. $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
  223. if ( ! $dims )
  224. return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
  225. list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
  226. if ( $crop ) {
  227. return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
  228. }
  229. // Execute the resize
  230. $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
  231. if ( is_wp_error( $thumb_result ) ) {
  232. return $thumb_result;
  233. }
  234. return $this->update_size( $dst_w, $dst_h );
  235. }
  236. /**
  237. * Efficiently resize the current image
  238. *
  239. * This is a WordPress specific implementation of Imagick::thumbnailImage(),
  240. * which resizes an image to given dimensions and removes any associated profiles.
  241. *
  242. * @since 4.5.0
  243. *
  244. * @param int $dst_w The destination width.
  245. * @param int $dst_h The destination height.
  246. * @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'.
  247. * @param bool $strip_meta Optional. Strip all profiles, excluding color profiles, from the image. Default true.
  248. * @return bool|WP_Error
  249. */
  250. protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) {
  251. $allowed_filters = array(
  252. 'FILTER_POINT',
  253. 'FILTER_BOX',
  254. 'FILTER_TRIANGLE',
  255. 'FILTER_HERMITE',
  256. 'FILTER_HANNING',
  257. 'FILTER_HAMMING',
  258. 'FILTER_BLACKMAN',
  259. 'FILTER_GAUSSIAN',
  260. 'FILTER_QUADRATIC',
  261. 'FILTER_CUBIC',
  262. 'FILTER_CATROM',
  263. 'FILTER_MITCHELL',
  264. 'FILTER_LANCZOS',
  265. 'FILTER_BESSEL',
  266. 'FILTER_SINC',
  267. );
  268. /**
  269. * Set the filter value if '$filter_name' name is in our whitelist and the related
  270. * Imagick constant is defined or fall back to our default filter.
  271. */
  272. if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) {
  273. $filter = constant( 'Imagick::' . $filter_name );
  274. } else {
  275. $filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
  276. }
  277. /**
  278. * Filters whether to strip metadata from images when they're resized.
  279. *
  280. * This filter only applies when resizing using the Imagick editor since GD
  281. * always strips profiles by default.
  282. *
  283. * @since 4.5.0
  284. *
  285. * @param bool $strip_meta Whether to strip image metadata during resizing. Default true.
  286. */
  287. if ( apply_filters( 'image_strip_meta', $strip_meta ) ) {
  288. $this->strip_meta(); // Fail silently if not supported.
  289. }
  290. try {
  291. /*
  292. * To be more efficient, resample large images to 5x the destination size before resizing
  293. * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111),
  294. * unless we would be resampling to a scale smaller than 128x128.
  295. */
  296. if ( is_callable( array( $this->image, 'sampleImage' ) ) ) {
  297. $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] );
  298. $sample_factor = 5;
  299. if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) {
  300. $this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor );
  301. }
  302. }
  303. /*
  304. * Use resizeImage() when it's available and a valid filter value is set.
  305. * Otherwise, fall back to the scaleImage() method for resizing, which
  306. * results in better image quality over resizeImage() with default filter
  307. * settings and retains backward compatibility with pre 4.5 functionality.
  308. */
  309. if ( is_callable( array( $this->image, 'resizeImage' ) ) && $filter ) {
  310. $this->image->setOption( 'filter:support', '2.0' );
  311. $this->image->resizeImage( $dst_w, $dst_h, $filter, 1 );
  312. } else {
  313. $this->image->scaleImage( $dst_w, $dst_h );
  314. }
  315. // Set appropriate quality settings after resizing.
  316. if ( 'image/jpeg' == $this->mime_type ) {
  317. if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
  318. $this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
  319. }
  320. $this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
  321. }
  322. if ( 'image/png' === $this->mime_type ) {
  323. $this->image->setOption( 'png:compression-filter', '5' );
  324. $this->image->setOption( 'png:compression-level', '9' );
  325. $this->image->setOption( 'png:compression-strategy', '1' );
  326. $this->image->setOption( 'png:exclude-chunk', 'all' );
  327. }
  328. /*
  329. * If alpha channel is not defined, set it opaque.
  330. *
  331. * Note that Imagick::getImageAlphaChannel() is only available if Imagick
  332. * has been compiled against ImageMagick version 6.4.0 or newer.
  333. */
  334. if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) )
  335. && is_callable( array( $this->image, 'setImageAlphaChannel' ) )
  336. && defined( 'Imagick::ALPHACHANNEL_UNDEFINED' )
  337. && defined( 'Imagick::ALPHACHANNEL_OPAQUE' )
  338. ) {
  339. if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
  340. $this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
  341. }
  342. }
  343. // Limit the bit depth of resized images to 8 bits per channel.
  344. if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
  345. if ( 8 < $this->image->getImageDepth() ) {
  346. $this->image->setImageDepth( 8 );
  347. }
  348. }
  349. if ( is_callable( array( $this->image, 'setInterlaceScheme' ) ) && defined( 'Imagick::INTERLACE_NO' ) ) {
  350. $this->image->setInterlaceScheme( Imagick::INTERLACE_NO );
  351. }
  352. }
  353. catch ( Exception $e ) {
  354. return new WP_Error( 'image_resize_error', $e->getMessage() );
  355. }
  356. }
  357. /**
  358. * Resize multiple images from a single source.
  359. *
  360. * @since 3.5.0
  361. *
  362. * @param array $sizes {
  363. * An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'.
  364. *
  365. * Either a height or width must be provided.
  366. * If one of the two is set to null, the resize will
  367. * maintain aspect ratio according to the provided dimension.
  368. *
  369. * @type array $size {
  370. * Array of height, width values, and whether to crop.
  371. *
  372. * @type int $width Image width. Optional if `$height` is specified.
  373. * @type int $height Image height. Optional if `$width` is specified.
  374. * @type bool $crop Optional. Whether to crop the image. Default false.
  375. * }
  376. * }
  377. * @return array An array of resized images' metadata by size.
  378. */
  379. public function multi_resize( $sizes ) {
  380. $metadata = array();
  381. $orig_size = $this->size;
  382. $orig_image = $this->image->getImage();
  383. foreach ( $sizes as $size => $size_data ) {
  384. if ( ! $this->image )
  385. $this->image = $orig_image->getImage();
  386. if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
  387. continue;
  388. }
  389. if ( ! isset( $size_data['width'] ) ) {
  390. $size_data['width'] = null;
  391. }
  392. if ( ! isset( $size_data['height'] ) ) {
  393. $size_data['height'] = null;
  394. }
  395. if ( ! isset( $size_data['crop'] ) ) {
  396. $size_data['crop'] = false;
  397. }
  398. $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
  399. $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) );
  400. if ( ! is_wp_error( $resize_result ) && ! $duplicate ) {
  401. $resized = $this->_save( $this->image );
  402. $this->image->clear();
  403. $this->image->destroy();
  404. $this->image = null;
  405. if ( ! is_wp_error( $resized ) && $resized ) {
  406. unset( $resized['path'] );
  407. $metadata[$size] = $resized;
  408. }
  409. }
  410. $this->size = $orig_size;
  411. }
  412. $this->image = $orig_image;
  413. return $metadata;
  414. }
  415. /**
  416. * Crops Image.
  417. *
  418. * @since 3.5.0
  419. *
  420. * @param int $src_x The start x position to crop from.
  421. * @param int $src_y The start y position to crop from.
  422. * @param int $src_w The width to crop.
  423. * @param int $src_h The height to crop.
  424. * @param int $dst_w Optional. The destination width.
  425. * @param int $dst_h Optional. The destination height.
  426. * @param bool $src_abs Optional. If the source crop points are absolute.
  427. * @return bool|WP_Error
  428. */
  429. public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
  430. if ( $src_abs ) {
  431. $src_w -= $src_x;
  432. $src_h -= $src_y;
  433. }
  434. try {
  435. $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
  436. $this->image->setImagePage( $src_w, $src_h, 0, 0);
  437. if ( $dst_w || $dst_h ) {
  438. // If destination width/height isn't specified, use same as
  439. // width/height from source.
  440. if ( ! $dst_w )
  441. $dst_w = $src_w;
  442. if ( ! $dst_h )
  443. $dst_h = $src_h;
  444. $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
  445. if ( is_wp_error( $thumb_result ) ) {
  446. return $thumb_result;
  447. }
  448. return $this->update_size();
  449. }
  450. }
  451. catch ( Exception $e ) {
  452. return new WP_Error( 'image_crop_error', $e->getMessage() );
  453. }
  454. return $this->update_size();
  455. }
  456. /**
  457. * Rotates current image counter-clockwise by $angle.
  458. *
  459. * @since 3.5.0
  460. *
  461. * @param float $angle
  462. * @return true|WP_Error
  463. */
  464. public function rotate( $angle ) {
  465. /**
  466. * $angle is 360-$angle because Imagick rotates clockwise
  467. * (GD rotates counter-clockwise)
  468. */
  469. try {
  470. $this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
  471. // Normalise Exif orientation data so that display is consistent across devices.
  472. if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
  473. $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
  474. }
  475. // Since this changes the dimensions of the image, update the size.
  476. $result = $this->update_size();
  477. if ( is_wp_error( $result ) )
  478. return $result;
  479. $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
  480. }
  481. catch ( Exception $e ) {
  482. return new WP_Error( 'image_rotate_error', $e->getMessage() );
  483. }
  484. return true;
  485. }
  486. /**
  487. * Flips current image.
  488. *
  489. * @since 3.5.0
  490. *
  491. * @param bool $horz Flip along Horizontal Axis
  492. * @param bool $vert Flip along Vertical Axis
  493. * @return true|WP_Error
  494. */
  495. public function flip( $horz, $vert ) {
  496. try {
  497. if ( $horz )
  498. $this->image->flipImage();
  499. if ( $vert )
  500. $this->image->flopImage();
  501. }
  502. catch ( Exception $e ) {
  503. return new WP_Error( 'image_flip_error', $e->getMessage() );
  504. }
  505. return true;
  506. }
  507. /**
  508. * Saves current image to file.
  509. *
  510. * @since 3.5.0
  511. *
  512. * @param string $destfilename
  513. * @param string $mime_type
  514. * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
  515. */
  516. public function save( $destfilename = null, $mime_type = null ) {
  517. $saved = $this->_save( $this->image, $destfilename, $mime_type );
  518. if ( ! is_wp_error( $saved ) ) {
  519. $this->file = $saved['path'];
  520. $this->mime_type = $saved['mime-type'];
  521. try {
  522. $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
  523. }
  524. catch ( Exception $e ) {
  525. return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
  526. }
  527. }
  528. return $saved;
  529. }
  530. /**
  531. *
  532. * @param Imagick $image
  533. * @param string $filename
  534. * @param string $mime_type
  535. * @return array|WP_Error
  536. */
  537. protected function _save( $image, $filename = null, $mime_type = null ) {
  538. list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
  539. if ( ! $filename )
  540. $filename = $this->generate_filename( null, null, $extension );
  541. try {
  542. // Store initial Format
  543. $orig_format = $this->image->getImageFormat();
  544. $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
  545. $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
  546. // Reset original Format
  547. $this->image->setImageFormat( $orig_format );
  548. }
  549. catch ( Exception $e ) {
  550. return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
  551. }
  552. // Set correct file permissions
  553. $stat = stat( dirname( $filename ) );
  554. $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
  555. @ chmod( $filename, $perms );
  556. /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
  557. return array(
  558. 'path' => $filename,
  559. 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
  560. 'width' => $this->size['width'],
  561. 'height' => $this->size['height'],
  562. 'mime-type' => $mime_type,
  563. );
  564. }
  565. /**
  566. * Streams current image to browser.
  567. *
  568. * @since 3.5.0
  569. *
  570. * @param string $mime_type The mime type of the image.
  571. * @return bool|WP_Error True on success, WP_Error object on failure.
  572. */
  573. public function stream( $mime_type = null ) {
  574. list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
  575. try {
  576. // Temporarily change format for stream
  577. $this->image->setImageFormat( strtoupper( $extension ) );
  578. // Output stream of image content
  579. header( "Content-Type: $mime_type" );
  580. print $this->image->getImageBlob();
  581. // Reset Image to original Format
  582. $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
  583. }
  584. catch ( Exception $e ) {
  585. return new WP_Error( 'image_stream_error', $e->getMessage() );
  586. }
  587. return true;
  588. }
  589. /**
  590. * Strips all image meta except color profiles from an image.
  591. *
  592. * @since 4.5.0
  593. *
  594. * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
  595. */
  596. protected function strip_meta() {
  597. if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
  598. /* translators: %s: ImageMagick method name */
  599. return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) );
  600. }
  601. if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
  602. /* translators: %s: ImageMagick method name */
  603. return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) );
  604. }
  605. /*
  606. * Protect a few profiles from being stripped for the following reasons:
  607. *
  608. * - icc: Color profile information
  609. * - icm: Color profile information
  610. * - iptc: Copyright data
  611. * - exif: Orientation data
  612. * - xmp: Rights usage data
  613. */
  614. $protected_profiles = array(
  615. 'icc',
  616. 'icm',
  617. 'iptc',
  618. 'exif',
  619. 'xmp',
  620. );
  621. try {
  622. // Strip profiles.
  623. foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
  624. if ( ! in_array( $key, $protected_profiles ) ) {
  625. $this->image->removeImageProfile( $key );
  626. }
  627. }
  628. } catch ( Exception $e ) {
  629. return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
  630. }
  631. return true;
  632. }
  633. /**
  634. * Sets up Imagick for PDF processing.
  635. * Increases rendering DPI and only loads first page.
  636. *
  637. * @since 4.7.0
  638. *
  639. * @return string|WP_Error File to load or WP_Error on failure.
  640. */
  641. protected function pdf_setup() {
  642. try {
  643. // By default, PDFs are rendered in a very low resolution.
  644. // We want the thumbnail to be readable, so increase the rendering DPI.
  645. $this->image->setResolution( 128, 128 );
  646. // Only load the first page.
  647. return $this->file . '[0]';
  648. }
  649. catch ( Exception $e ) {
  650. return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file );
  651. }
  652. }
  653. }