3 * Base WordPress Image Editor
6 * @subpackage Image_Editor
10 * Base image editor class from which implementations extend
14 abstract class WP_Image_Editor {
15 protected $file = null;
16 protected $size = null;
17 protected $mime_type = null;
18 protected $default_mime_type = 'image/jpeg';
19 protected $quality = false;
20 protected $default_quality = 82;
23 * Each instance handles a single file.
25 * @param string $file Path to the file to load.
27 public function __construct( $file ) {
32 * Checks to see if current environment supports the editor chosen.
33 * Must be overridden in a sub-class.
44 public static function test( $args = array() ) {
49 * Checks to see if editor supports the mime-type specified.
50 * Must be overridden in a sub-class.
58 * @param string $mime_type
61 public static function supports_mime_type( $mime_type ) {
66 * Loads image from $this->file into editor.
72 * @return bool|WP_Error True if loaded; WP_Error on failure.
74 abstract public function load();
77 * Saves current image to file.
83 * @param string $destfilename
84 * @param string $mime_type
85 * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
87 abstract public function save( $destfilename = null, $mime_type = null );
90 * Resizes current image.
92 * At minimum, either a height or width must be provided.
93 * If one of the two is set to null, the resize will
94 * maintain aspect ratio according to the provided dimension.
100 * @param int|null $max_w Image width.
101 * @param int|null $max_h Image height.
103 * @return bool|WP_Error
105 abstract public function resize( $max_w, $max_h, $crop = false );
108 * Resize multiple images from a single source.
114 * @param array $sizes {
115 * An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
117 * @type array $size {
118 * @type int $width Image width.
119 * @type int $height Image height.
120 * @type bool $crop Optional. Whether to crop the image. Default false.
123 * @return array An array of resized images metadata by size.
125 abstract public function multi_resize( $sizes );
134 * @param int $src_x The start x position to crop from.
135 * @param int $src_y The start y position to crop from.
136 * @param int $src_w The width to crop.
137 * @param int $src_h The height to crop.
138 * @param int $dst_w Optional. The destination width.
139 * @param int $dst_h Optional. The destination height.
140 * @param bool $src_abs Optional. If the source crop points are absolute.
141 * @return bool|WP_Error
143 abstract public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false );
146 * Rotates current image counter-clockwise by $angle.
152 * @param float $angle
153 * @return bool|WP_Error
155 abstract public function rotate( $angle );
158 * Flips current image.
164 * @param bool $horz Flip along Horizontal Axis
165 * @param bool $vert Flip along Vertical Axis
166 * @return bool|WP_Error
168 abstract public function flip( $horz, $vert );
171 * Streams current image to browser.
177 * @param string $mime_type
178 * @return bool|WP_Error
180 abstract public function stream( $mime_type = null );
183 * Gets dimensions of image.
188 * @return array {'width'=>int, 'height'=>int}
190 public function get_size() {
195 * Sets current image size.
204 protected function update_size( $width = null, $height = null ) {
206 'width' => (int) $width,
207 'height' => (int) $height
213 * Gets the Image Compression quality on a 1-100% scale.
218 * @return int $quality Compression Quality. Range: [1,100]
220 public function get_quality() {
221 if ( ! $this->quality ) {
222 $this->set_quality();
225 return $this->quality;
229 * Sets Image Compression quality on a 1-100% scale.
234 * @param int $quality Compression Quality. Range: [1,100]
235 * @return true|WP_Error True if set successfully; WP_Error on failure.
237 public function set_quality( $quality = null ) {
238 if ( null === $quality ) {
240 * Filters the default image compression quality setting.
242 * Applies only during initial editor instantiation, or when set_quality() is run
243 * manually without the `$quality` argument.
245 * set_quality() has priority over the filter.
249 * @param int $quality Quality level between 1 (low) and 100 (high).
250 * @param string $mime_type Image mime type.
252 $quality = apply_filters( 'wp_editor_set_quality', $this->default_quality, $this->mime_type );
254 if ( 'image/jpeg' == $this->mime_type ) {
256 * Filters the JPEG compression quality for backward-compatibility.
258 * Applies only during initial editor instantiation, or when set_quality() is run
259 * manually without the `$quality` argument.
261 * set_quality() has priority over the filter.
263 * The filter is evaluated under two contexts: 'image_resize', and 'edit_image',
264 * (when a JPEG image is saved to file).
268 * @param int $quality Quality level between 0 (low) and 100 (high) of the JPEG.
269 * @param string $context Context of the filter.
271 $quality = apply_filters( 'jpeg_quality', $quality, 'image_resize' );
274 if ( $quality < 0 || $quality > 100 ) {
275 $quality = $this->default_quality;
279 // Allow 0, but squash to 1 due to identical images in GD, and for backward compatibility.
280 if ( 0 === $quality ) {
284 if ( ( $quality >= 1 ) && ( $quality <= 100 ) ) {
285 $this->quality = $quality;
288 return new WP_Error( 'invalid_image_quality', __('Attempted to set image quality outside of the range [1,100].') );
293 * Returns preferred mime-type and extension based on provided
294 * file's extension and mime, or current file's extension and mime.
296 * Will default to $this->default_mime_type if requested is not supported.
298 * Provides corrected filename only if filename is provided.
303 * @param string $filename
304 * @param string $mime_type
305 * @return array { filename|null, extension, mime-type }
307 protected function get_output_format( $filename = null, $mime_type = null ) {
310 // By default, assume specified type takes priority
312 $new_ext = $this->get_extension( $mime_type );
316 $file_ext = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
317 $file_mime = $this->get_mime_type( $file_ext );
320 // If no file specified, grab editor's current extension and mime-type.
321 $file_ext = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
322 $file_mime = $this->mime_type;
325 // Check to see if specified mime-type is the same as type implied by
326 // file extension. If so, prefer extension from file.
327 if ( ! $mime_type || ( $file_mime == $mime_type ) ) {
328 $mime_type = $file_mime;
329 $new_ext = $file_ext;
332 // Double-check that the mime-type selected is supported by the editor.
333 // If not, choose a default instead.
334 if ( ! $this->supports_mime_type( $mime_type ) ) {
336 * Filters default mime type prior to getting the file extension.
338 * @see wp_get_mime_types()
342 * @param string $mime_type Mime type string.
344 $mime_type = apply_filters( 'image_editor_default_mime_type', $this->default_mime_type );
345 $new_ext = $this->get_extension( $mime_type );
350 $info = pathinfo( $filename );
351 $dir = $info['dirname'];
353 if ( isset( $info['extension'] ) )
354 $ext = $info['extension'];
356 $filename = trailingslashit( $dir ) . wp_basename( $filename, ".$ext" ) . ".{$new_ext}";
359 return array( $filename, $new_ext, $mime_type );
363 * Builds an output filename based on current file, and adding proper suffix
368 * @param string $suffix
369 * @param string $dest_path
370 * @param string $extension
371 * @return string filename
373 public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
374 // $suffix will be appended to the destination filename, just before the extension
376 $suffix = $this->get_suffix();
378 $info = pathinfo( $this->file );
379 $dir = $info['dirname'];
380 $ext = $info['extension'];
382 $name = wp_basename( $this->file, ".$ext" );
383 $new_ext = strtolower( $extension ? $extension : $ext );
385 if ( ! is_null( $dest_path ) && $_dest_path = realpath( $dest_path ) )
388 return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
392 * Builds and returns proper suffix for file based on height and width.
397 * @return false|string suffix
399 public function get_suffix() {
400 if ( ! $this->get_size() )
403 return "{$this->size['width']}x{$this->size['height']}";
407 * Either calls editor's save function or handles file as a stream.
412 * @param string|stream $filename
413 * @param callable $function
414 * @param array $arguments
417 protected function make_image( $filename, $function, $arguments ) {
418 if ( $stream = wp_is_stream( $filename ) ) {
421 // The directory containing the original file may no longer exist when using a replication plugin.
422 wp_mkdir_p( dirname( $filename ) );
425 $result = call_user_func_array( $function, $arguments );
427 if ( $result && $stream ) {
428 $contents = ob_get_contents();
430 $fp = fopen( $filename, 'w' );
435 fwrite( $fp, $contents );
447 * Returns first matched mime-type from extension,
448 * as mapped from wp_get_mime_types()
455 * @param string $extension
456 * @return string|false
458 protected static function get_mime_type( $extension = null ) {
462 $mime_types = wp_get_mime_types();
463 $extensions = array_keys( $mime_types );
465 foreach ( $extensions as $_extension ) {
466 if ( preg_match( "/{$extension}/i", $_extension ) ) {
467 return $mime_types[$_extension];
475 * Returns first matched extension from Mime-type,
476 * as mapped from wp_get_mime_types()
483 * @param string $mime_type
484 * @return string|false
486 protected static function get_extension( $mime_type = null ) {
487 $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types() ) );
489 if ( empty( $extensions[0] ) )
492 return $extensions[0];