class-wp-image-editor.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <?php
  2. /**
  3. * Base WordPress Image Editor
  4. *
  5. * @package WordPress
  6. * @subpackage Image_Editor
  7. */
  8. /**
  9. * Base image editor class from which implementations extend
  10. *
  11. * @since 3.5.0
  12. */
  13. abstract class WP_Image_Editor {
  14. protected $file = null;
  15. protected $size = null;
  16. protected $mime_type = null;
  17. protected $default_mime_type = 'image/jpeg';
  18. protected $quality = false;
  19. protected $default_quality = 82;
  20. /**
  21. * Each instance handles a single file.
  22. *
  23. * @param string $file Path to the file to load.
  24. */
  25. public function __construct( $file ) {
  26. $this->file = $file;
  27. }
  28. /**
  29. * Checks to see if current environment supports the editor chosen.
  30. * Must be overridden in a sub-class.
  31. *
  32. * @since 3.5.0
  33. *
  34. * @static
  35. * @abstract
  36. *
  37. * @param array $args
  38. * @return bool
  39. */
  40. public static function test( $args = array() ) {
  41. return false;
  42. }
  43. /**
  44. * Checks to see if editor supports the mime-type specified.
  45. * Must be overridden in a sub-class.
  46. *
  47. * @since 3.5.0
  48. *
  49. * @static
  50. * @abstract
  51. *
  52. * @param string $mime_type
  53. * @return bool
  54. */
  55. public static function supports_mime_type( $mime_type ) {
  56. return false;
  57. }
  58. /**
  59. * Loads image from $this->file into editor.
  60. *
  61. * @since 3.5.0
  62. * @abstract
  63. *
  64. * @return bool|WP_Error True if loaded; WP_Error on failure.
  65. */
  66. abstract public function load();
  67. /**
  68. * Saves current image to file.
  69. *
  70. * @since 3.5.0
  71. * @abstract
  72. *
  73. * @param string $destfilename
  74. * @param string $mime_type
  75. * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
  76. */
  77. abstract public function save( $destfilename = null, $mime_type = null );
  78. /**
  79. * Resizes current image.
  80. *
  81. * At minimum, either a height or width must be provided.
  82. * If one of the two is set to null, the resize will
  83. * maintain aspect ratio according to the provided dimension.
  84. *
  85. * @since 3.5.0
  86. * @abstract
  87. *
  88. * @param int|null $max_w Image width.
  89. * @param int|null $max_h Image height.
  90. * @param bool $crop
  91. * @return bool|WP_Error
  92. */
  93. abstract public function resize( $max_w, $max_h, $crop = false );
  94. /**
  95. * Resize multiple images from a single source.
  96. *
  97. * @since 3.5.0
  98. * @abstract
  99. *
  100. * @param array $sizes {
  101. * An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
  102. *
  103. * @type array $size {
  104. * @type int $width Image width.
  105. * @type int $height Image height.
  106. * @type bool $crop Optional. Whether to crop the image. Default false.
  107. * }
  108. * }
  109. * @return array An array of resized images metadata by size.
  110. */
  111. abstract public function multi_resize( $sizes );
  112. /**
  113. * Crops Image.
  114. *
  115. * @since 3.5.0
  116. * @abstract
  117. *
  118. * @param int $src_x The start x position to crop from.
  119. * @param int $src_y The start y position to crop from.
  120. * @param int $src_w The width to crop.
  121. * @param int $src_h The height to crop.
  122. * @param int $dst_w Optional. The destination width.
  123. * @param int $dst_h Optional. The destination height.
  124. * @param bool $src_abs Optional. If the source crop points are absolute.
  125. * @return bool|WP_Error
  126. */
  127. abstract public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false );
  128. /**
  129. * Rotates current image counter-clockwise by $angle.
  130. *
  131. * @since 3.5.0
  132. * @abstract
  133. *
  134. * @param float $angle
  135. * @return bool|WP_Error
  136. */
  137. abstract public function rotate( $angle );
  138. /**
  139. * Flips current image.
  140. *
  141. * @since 3.5.0
  142. * @abstract
  143. *
  144. * @param bool $horz Flip along Horizontal Axis
  145. * @param bool $vert Flip along Vertical Axis
  146. * @return bool|WP_Error
  147. */
  148. abstract public function flip( $horz, $vert );
  149. /**
  150. * Streams current image to browser.
  151. *
  152. * @since 3.5.0
  153. * @abstract
  154. *
  155. * @param string $mime_type The mime type of the image.
  156. * @return bool|WP_Error True on success, WP_Error object or false on failure.
  157. */
  158. abstract public function stream( $mime_type = null );
  159. /**
  160. * Gets dimensions of image.
  161. *
  162. * @since 3.5.0
  163. *
  164. * @return array {'width'=>int, 'height'=>int}
  165. */
  166. public function get_size() {
  167. return $this->size;
  168. }
  169. /**
  170. * Sets current image size.
  171. *
  172. * @since 3.5.0
  173. *
  174. * @param int $width
  175. * @param int $height
  176. * @return true
  177. */
  178. protected function update_size( $width = null, $height = null ) {
  179. $this->size = array(
  180. 'width' => (int) $width,
  181. 'height' => (int) $height
  182. );
  183. return true;
  184. }
  185. /**
  186. * Gets the Image Compression quality on a 1-100% scale.
  187. *
  188. * @since 4.0.0
  189. *
  190. * @return int $quality Compression Quality. Range: [1,100]
  191. */
  192. public function get_quality() {
  193. if ( ! $this->quality ) {
  194. $this->set_quality();
  195. }
  196. return $this->quality;
  197. }
  198. /**
  199. * Sets Image Compression quality on a 1-100% scale.
  200. *
  201. * @since 3.5.0
  202. *
  203. * @param int $quality Compression Quality. Range: [1,100]
  204. * @return true|WP_Error True if set successfully; WP_Error on failure.
  205. */
  206. public function set_quality( $quality = null ) {
  207. if ( null === $quality ) {
  208. /**
  209. * Filters the default image compression quality setting.
  210. *
  211. * Applies only during initial editor instantiation, or when set_quality() is run
  212. * manually without the `$quality` argument.
  213. *
  214. * set_quality() has priority over the filter.
  215. *
  216. * @since 3.5.0
  217. *
  218. * @param int $quality Quality level between 1 (low) and 100 (high).
  219. * @param string $mime_type Image mime type.
  220. */
  221. $quality = apply_filters( 'wp_editor_set_quality', $this->default_quality, $this->mime_type );
  222. if ( 'image/jpeg' == $this->mime_type ) {
  223. /**
  224. * Filters the JPEG compression quality for backward-compatibility.
  225. *
  226. * Applies only during initial editor instantiation, or when set_quality() is run
  227. * manually without the `$quality` argument.
  228. *
  229. * set_quality() has priority over the filter.
  230. *
  231. * The filter is evaluated under two contexts: 'image_resize', and 'edit_image',
  232. * (when a JPEG image is saved to file).
  233. *
  234. * @since 2.5.0
  235. *
  236. * @param int $quality Quality level between 0 (low) and 100 (high) of the JPEG.
  237. * @param string $context Context of the filter.
  238. */
  239. $quality = apply_filters( 'jpeg_quality', $quality, 'image_resize' );
  240. }
  241. if ( $quality < 0 || $quality > 100 ) {
  242. $quality = $this->default_quality;
  243. }
  244. }
  245. // Allow 0, but squash to 1 due to identical images in GD, and for backward compatibility.
  246. if ( 0 === $quality ) {
  247. $quality = 1;
  248. }
  249. if ( ( $quality >= 1 ) && ( $quality <= 100 ) ) {
  250. $this->quality = $quality;
  251. return true;
  252. } else {
  253. return new WP_Error( 'invalid_image_quality', __('Attempted to set image quality outside of the range [1,100].') );
  254. }
  255. }
  256. /**
  257. * Returns preferred mime-type and extension based on provided
  258. * file's extension and mime, or current file's extension and mime.
  259. *
  260. * Will default to $this->default_mime_type if requested is not supported.
  261. *
  262. * Provides corrected filename only if filename is provided.
  263. *
  264. * @since 3.5.0
  265. *
  266. * @param string $filename
  267. * @param string $mime_type
  268. * @return array { filename|null, extension, mime-type }
  269. */
  270. protected function get_output_format( $filename = null, $mime_type = null ) {
  271. $new_ext = null;
  272. // By default, assume specified type takes priority
  273. if ( $mime_type ) {
  274. $new_ext = $this->get_extension( $mime_type );
  275. }
  276. if ( $filename ) {
  277. $file_ext = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
  278. $file_mime = $this->get_mime_type( $file_ext );
  279. }
  280. else {
  281. // If no file specified, grab editor's current extension and mime-type.
  282. $file_ext = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
  283. $file_mime = $this->mime_type;
  284. }
  285. // Check to see if specified mime-type is the same as type implied by
  286. // file extension. If so, prefer extension from file.
  287. if ( ! $mime_type || ( $file_mime == $mime_type ) ) {
  288. $mime_type = $file_mime;
  289. $new_ext = $file_ext;
  290. }
  291. // Double-check that the mime-type selected is supported by the editor.
  292. // If not, choose a default instead.
  293. if ( ! $this->supports_mime_type( $mime_type ) ) {
  294. /**
  295. * Filters default mime type prior to getting the file extension.
  296. *
  297. * @see wp_get_mime_types()
  298. *
  299. * @since 3.5.0
  300. *
  301. * @param string $mime_type Mime type string.
  302. */
  303. $mime_type = apply_filters( 'image_editor_default_mime_type', $this->default_mime_type );
  304. $new_ext = $this->get_extension( $mime_type );
  305. }
  306. if ( $filename ) {
  307. $dir = pathinfo( $filename, PATHINFO_DIRNAME );
  308. $ext = pathinfo( $filename, PATHINFO_EXTENSION );
  309. $filename = trailingslashit( $dir ) . wp_basename( $filename, ".$ext" ) . ".{$new_ext}";
  310. }
  311. return array( $filename, $new_ext, $mime_type );
  312. }
  313. /**
  314. * Builds an output filename based on current file, and adding proper suffix
  315. *
  316. * @since 3.5.0
  317. *
  318. * @param string $suffix
  319. * @param string $dest_path
  320. * @param string $extension
  321. * @return string filename
  322. */
  323. public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
  324. // $suffix will be appended to the destination filename, just before the extension
  325. if ( ! $suffix )
  326. $suffix = $this->get_suffix();
  327. $dir = pathinfo( $this->file, PATHINFO_DIRNAME );
  328. $ext = pathinfo( $this->file, PATHINFO_EXTENSION );
  329. $name = wp_basename( $this->file, ".$ext" );
  330. $new_ext = strtolower( $extension ? $extension : $ext );
  331. if ( ! is_null( $dest_path ) && $_dest_path = realpath( $dest_path ) )
  332. $dir = $_dest_path;
  333. return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
  334. }
  335. /**
  336. * Builds and returns proper suffix for file based on height and width.
  337. *
  338. * @since 3.5.0
  339. *
  340. * @return false|string suffix
  341. */
  342. public function get_suffix() {
  343. if ( ! $this->get_size() )
  344. return false;
  345. return "{$this->size['width']}x{$this->size['height']}";
  346. }
  347. /**
  348. * Either calls editor's save function or handles file as a stream.
  349. *
  350. * @since 3.5.0
  351. *
  352. * @param string|stream $filename
  353. * @param callable $function
  354. * @param array $arguments
  355. * @return bool
  356. */
  357. protected function make_image( $filename, $function, $arguments ) {
  358. if ( $stream = wp_is_stream( $filename ) ) {
  359. ob_start();
  360. } else {
  361. // The directory containing the original file may no longer exist when using a replication plugin.
  362. wp_mkdir_p( dirname( $filename ) );
  363. }
  364. $result = call_user_func_array( $function, $arguments );
  365. if ( $result && $stream ) {
  366. $contents = ob_get_contents();
  367. $fp = fopen( $filename, 'w' );
  368. if ( ! $fp )
  369. return false;
  370. fwrite( $fp, $contents );
  371. fclose( $fp );
  372. }
  373. if ( $stream ) {
  374. ob_end_clean();
  375. }
  376. return $result;
  377. }
  378. /**
  379. * Returns first matched mime-type from extension,
  380. * as mapped from wp_get_mime_types()
  381. *
  382. * @since 3.5.0
  383. *
  384. * @static
  385. *
  386. * @param string $extension
  387. * @return string|false
  388. */
  389. protected static function get_mime_type( $extension = null ) {
  390. if ( ! $extension )
  391. return false;
  392. $mime_types = wp_get_mime_types();
  393. $extensions = array_keys( $mime_types );
  394. foreach ( $extensions as $_extension ) {
  395. if ( preg_match( "/{$extension}/i", $_extension ) ) {
  396. return $mime_types[$_extension];
  397. }
  398. }
  399. return false;
  400. }
  401. /**
  402. * Returns first matched extension from Mime-type,
  403. * as mapped from wp_get_mime_types()
  404. *
  405. * @since 3.5.0
  406. *
  407. * @static
  408. *
  409. * @param string $mime_type
  410. * @return string|false
  411. */
  412. protected static function get_extension( $mime_type = null ) {
  413. $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types() ) );
  414. if ( empty( $extensions[0] ) )
  415. return false;
  416. return $extensions[0];
  417. }
  418. }