Ticket #6821: 6821.9.2.diff

File 6821.9.2.diff, 55.8 KB (added by DH-Shredder, 8 months ago)

Fix Warning for files with no extension, and use trailingslashit. Props markoheijnen.

Line 
1diff --git wp-admin/includes/image-edit.php wp-admin/includes/image-edit.php
2index 8185c20..75b6834 100644
3--- wp-admin/includes/image-edit.php
4+++ wp-admin/includes/image-edit.php
5@@ -197,39 +197,83 @@ function wp_image_editor($post_id, $msg = false) {
6 <?php
7 }
8 
9-function wp_stream_image($image, $mime_type, $post_id) {
10-       $image = apply_filters('image_save_pre', $image, $post_id);
11-
12-       switch ( $mime_type ) {
13-               case 'image/jpeg':
14-                       header('Content-Type: image/jpeg');
15-                       return imagejpeg($image, null, 90);
16-               case 'image/png':
17-                       header('Content-Type: image/png');
18-                       return imagepng($image);
19-               case 'image/gif':
20-                       header('Content-Type: image/gif');
21-                       return imagegif($image);
22-               default:
23+/**
24+ * Streams image in WP_Image_Editor to browser.
25+ * Provided for backcompat reasons
26+ *
27+ * @param WP_Image_Editor $image
28+ * @param string $mime_type
29+ * @param int $post_id
30+ * @return boolean
31+ */
32+function wp_stream_image( $image, $mime_type, $post_id ) {
33+       if ( $image instanceof WP_Image_Editor ) {
34+               $image = apply_filters('image_editor_save_pre', $image, $post_id);
35+
36+               if ( is_wp_error( $image->stream( $mime_type ) ) )
37                        return false;
38+
39+               return true;
40+
41+    } else {
42+               _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
43+
44+               $image = apply_filters('image_save_pre', $image, $post_id);
45+
46+               switch ( $mime_type ) {
47+                       case 'image/jpeg':
48+                               header( 'Content-Type: image/jpeg' );
49+                               return imagejpeg( $image, null, 90 );
50+                       case 'image/png':
51+                               header( 'Content-Type: image/png' );
52+                               return imagepng( $image );
53+                       case 'image/gif':
54+                               header( 'Content-Type: image/gif' );
55+                               return imagegif( $image );
56+                       default:
57+                               return false;
58+               }
59        }
60 }
61 
62-function wp_save_image_file($filename, $image, $mime_type, $post_id) {
63-       $image = apply_filters('image_save_pre', $image, $post_id);
64-       $saved = apply_filters('wp_save_image_file', null, $filename, $image, $mime_type, $post_id);
65-       if ( null !== $saved )
66-               return $saved;
67-
68-       switch ( $mime_type ) {
69-               case 'image/jpeg':
70-                       return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) );
71-               case 'image/png':
72-                       return imagepng($image, $filename);
73-               case 'image/gif':
74-                       return imagegif($image, $filename);
75-               default:
76-                       return false;
77+/**
78+ * Saves Image to File
79+ * @TODO: Add mime_type support to WP_Image_Editor
80+ *
81+ * @param string $filename
82+ * @param WP_Image_Editor $image
83+ * @param string $mime_type
84+ * @param int $post_id
85+ * @return boolean
86+ */
87+function wp_save_image_file( $filename, $image, $mime_type, $post_id ) {
88+       if ( $image instanceof WP_Image_Editor ) {
89+               $image = apply_filters('image_editor_save_pre', $image, $post_id);
90+               $saved = apply_filters('wp_save_image_editor_file', null, $filename, $image, $mime_type, $post_id);
91+
92+               if ( null !== $saved )
93+                       return $saved;
94+
95+               return $image->save( $filename, $mime_type );
96+       } else {
97+               _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
98+
99+               $image = apply_filters('image_save_pre', $image, $post_id);
100+               $saved = apply_filters('wp_save_image_file', null, $filename, $image, $mime_type, $post_id);
101+
102+               if ( null !== $saved )
103+                       return $saved;
104+
105+               switch ( $mime_type ) {
106+                       case 'image/jpeg':
107+                               return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) );
108+                       case 'image/png':
109+                               return imagepng( $image, $filename );
110+                       case 'image/gif':
111+                               return imagegif( $image, $filename );
112+                       default:
113+                               return false;
114+               }
115        }
116 }
117 
118@@ -238,7 +282,9 @@ function _image_get_preview_ratio($w, $h) {
119        return $max > 400 ? (400 / $max) : 1;
120 }
121 
122+// @TODO: Returns GD resource, but is NOT public
123 function _rotate_image_resource($img, $angle) {
124+       _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::rotate' ) );
125        if ( function_exists('imagerotate') ) {
126                $rotated = imagerotate($img, $angle, 0);
127                if ( is_resource($rotated) ) {
128@@ -249,7 +295,18 @@ function _rotate_image_resource($img, $angle) {
129        return $img;
130 }
131 
132+/**
133+ * @TODO: Only used within image_edit_apply_changes
134+ *               and receives/returns GD Resource.
135+ *               Consider removal.
136+ *
137+ * @param GD_Resource $img
138+ * @param boolean $horz
139+ * @param boolean $vert
140+ * @return GD_Resource
141+ */
142 function _flip_image_resource($img, $horz, $vert) {
143+       _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::flip' ) );
144        $w = imagesx($img);
145        $h = imagesy($img);
146        $dst = wp_imagecreatetruecolor($w, $h);
147@@ -267,6 +324,18 @@ function _flip_image_resource($img, $horz, $vert) {
148        return $img;
149 }
150 
151+/**
152+ * @TODO: Only used within image_edit_apply_changes
153+ *               and receives/returns GD Resource.
154+ *               Consider removal.
155+ *
156+ * @param GD_Resource $img
157+ * @param float $x
158+ * @param float $y
159+ * @param float $w
160+ * @param float $h
161+ * @return GD_Resource
162+ */
163 function _crop_image_resource($img, $x, $y, $w, $h) {
164        $dst = wp_imagecreatetruecolor($w, $h);
165        if ( is_resource($dst) ) {
166@@ -278,10 +347,19 @@ function _crop_image_resource($img, $x, $y, $w, $h) {
167        return $img;
168 }
169 
170-function image_edit_apply_changes($img, $changes) {
171+/**
172+ * Performs group of changes on Editor specified.
173+ *
174+ * @param WP_Image_Editor $image
175+ * @param type $changes
176+ * @return WP_Image_Editor
177+ */
178+function image_edit_apply_changes( $image, $changes ) {
179+       if ( is_resource( $image ) )
180+               _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) );
181 
182        if ( !is_array($changes) )
183-               return $img;
184+               return $image;
185 
186        // expand change operations
187        foreach ( $changes as $key => $obj ) {
188@@ -326,55 +404,83 @@ function image_edit_apply_changes($img, $changes) {
189        }
190 
191        // image resource before applying the changes
192-       $img = apply_filters('image_edit_before_change', $img, $changes);
193+       if ( $image instanceof WP_Image_Editor )
194+               $image = apply_filters('wp_image_editor_before_change', $image, $changes);
195+       elseif ( is_resource( $image ) )
196+               $image = apply_filters('image_edit_before_change', $image, $changes);
197 
198        foreach ( $changes as $operation ) {
199                switch ( $operation->type ) {
200                        case 'rotate':
201-                               if ( $operation->angle != 0 )
202-                                       $img = _rotate_image_resource($img, $operation->angle);
203+                               if ( $operation->angle != 0 ) {
204+                                       if ( $image instanceof WP_Image_Editor )
205+                                               $image->rotate( $operation->angle );
206+                                       else
207+                                               $image = _rotate_image_resource( $image, $operation->angle );
208+                               }
209                                break;
210                        case 'flip':
211                                if ( $operation->axis != 0 )
212-                                       $img = _flip_image_resource($img, ($operation->axis & 1) != 0, ($operation->axis & 2) != 0);
213+                                       if ( $image instanceof WP_Image_Editor )
214+                                               $image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 );
215+                                       else
216+                                               $image = _flip_image_resource( $image, ( $operation->axis & 1 ) != 0, ( $operation->axis & 2 ) != 0 );
217                                break;
218                        case 'crop':
219                                $sel = $operation->sel;
220-                               $scale = 1 / _image_get_preview_ratio( imagesx($img), imagesy($img) ); // discard preview scaling
221-                               $img = _crop_image_resource($img, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale);
222+
223+                               if ( $image instanceof WP_Image_Editor ) {
224+                                       $size = $image->get_size();
225+                                       $w = $size['width'];
226+                                       $h = $size['height'];
227+
228+                                       $scale = 1 / _image_get_preview_ratio( $w, $h ); // discard preview scaling
229+                                       $image->crop( $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
230+                               } else {
231+                                       $scale = 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // discard preview scaling
232+                                       $image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
233+                               }
234                                break;
235                }
236        }
237 
238-       return $img;
239+       return $image;
240 }
241 
242-function stream_preview_image($post_id) {
243-       $post = get_post($post_id);
244+
245+/**
246+ * Streams image in post to browser, along with enqueued changes
247+ * in $_REQUEST['history']
248+ *
249+ * @param int $post_id
250+ * @return boolean
251+ */
252+function stream_preview_image( $post_id ) {
253+       $post = get_post( $post_id );
254        @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
255-       $img = load_image_to_edit( $post_id, $post->post_mime_type, array(400, 400) );
256 
257-       if ( !is_resource($img) )
258-               return false;
259+       $img = WP_Image_Editor::get_instance( _load_image_to_edit_path( $post_id ) );
260+
261+    if ( is_wp_error( $img ) )
262+        return false;
263 
264        $changes = !empty($_REQUEST['history']) ? json_decode( stripslashes($_REQUEST['history']) ) : null;
265        if ( $changes )
266-               $img = image_edit_apply_changes($img, $changes);
267+               $img = image_edit_apply_changes( $img, $changes );
268 
269        // scale the image
270-       $w = imagesx($img);
271-       $h = imagesy($img);
272-       $ratio = _image_get_preview_ratio($w, $h);
273+       $size = $img->get_size();
274+       $w = $size['width'];
275+       $h = $size['height'];
276+
277+       $ratio = _image_get_preview_ratio( $w, $h );
278        $w2 = $w * $ratio;
279        $h2 = $h * $ratio;
280 
281-       $preview = wp_imagecreatetruecolor($w2, $h2);
282-       imagecopyresampled( $preview, $img, 0, 0, 0, 0, $w2, $h2, $w, $h );
283-       wp_stream_image($preview, $post->post_mime_type, $post_id);
284+       if ( is_wp_error( $img->resize( $w2, $h2 ) ) )
285+               return false;
286 
287-       imagedestroy($preview);
288-       imagedestroy($img);
289-       return true;
290+       return wp_stream_image( $img, $post->post_mime_type, $post_id );
291 }
292 
293 function wp_restore_image($post_id) {
294@@ -450,14 +556,20 @@ function wp_restore_image($post_id) {
295        return $msg;
296 }
297 
298-function wp_save_image($post_id) {
299+/**
300+ * Saves image to post along with enqueued changes
301+ * in $_REQUEST['history']
302+ *
303+ * @param int $post_id
304+ * @return \stdClass
305+ */
306+function wp_save_image( $post_id ) {
307        $return = new stdClass;
308        $success = $delete = $scaled = $nocrop = false;
309-       $post = get_post($post_id);
310-       @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
311-       $img = load_image_to_edit($post_id, $post->post_mime_type);
312+       $post = get_post( $post_id );
313 
314-       if ( !is_resource($img) ) {
315+       $img = WP_Image_Editor::get_instance( _load_image_to_edit_path( $post_id, 'full' ) );
316+       if ( !$img ) {
317                $return->error = esc_js( __('Unable to create new image.') );
318                return $return;
319        }
320@@ -468,19 +580,16 @@ function wp_save_image($post_id) {
321        $scale = !empty($_REQUEST['do']) && 'scale' == $_REQUEST['do'];
322 
323        if ( $scale && $fwidth > 0 && $fheight > 0 ) {
324-               $sX = imagesx($img);
325-               $sY = imagesy($img);
326+               $size = $img->get_size();
327+               $sX = $size['width'];
328+               $sY = $size['height'];
329 
330                // check if it has roughly the same w / h ratio
331                $diff = round($sX / $sY, 2) - round($fwidth / $fheight, 2);
332                if ( -0.1 < $diff && $diff < 0.1 ) {
333                        // scale the full size image
334-                       $dst = wp_imagecreatetruecolor($fwidth, $fheight);
335-                       if ( imagecopyresampled( $dst, $img, 0, 0, 0, 0, $fwidth, $fheight, $sX, $sY ) ) {
336-                               imagedestroy($img);
337-                               $img = $dst;
338+                       if ( $img->resize( $fwidth, $fheight ) )
339                                $scaled = true;
340-                       }
341                }
342 
343                if ( !$scaled ) {
344@@ -551,11 +660,13 @@ function wp_save_image($post_id) {
345                if ( $tag )
346                        $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $path_parts['basename']);
347 
348-               $success = update_attached_file($post_id, $new_path);
349+               $success = update_attached_file( $post_id, $new_path );
350+
351+               $meta['file'] = _wp_relative_upload_path( $new_path );
352 
353-               $meta['file'] = _wp_relative_upload_path($new_path);
354-               $meta['width'] = imagesx($img);
355-               $meta['height'] = imagesy($img);
356+               $size = $img->get_size();
357+               $meta['width'] = $size['width'];
358+               $meta['height'] = $size['height'];
359 
360                if ( $success && ('nothumb' == $target || 'all' == $target) ) {
361                        $sizes = get_intermediate_image_sizes();
362@@ -570,10 +681,12 @@ function wp_save_image($post_id) {
363                $success = $delete = $nocrop = true;
364        }
365 
366-       if ( isset($sizes) ) {
367+       if ( isset( $sizes ) ) {
368+               $_sizes = array();
369+
370                foreach ( $sizes as $size ) {
371                        $tag = false;
372-                       if ( isset($meta['sizes'][$size]) ) {
373+                       if ( isset( $meta['sizes'][$size] ) ) {
374                                if ( isset($backup_sizes["$size-orig"]) ) {
375                                        if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] )
376                                                $tag = "$size-$suffix";
377@@ -586,17 +699,16 @@ function wp_save_image($post_id) {
378                        }
379 
380                        $crop = $nocrop ? false : get_option("{$size}_crop");
381-                       $resized = image_make_intermediate_size($new_path, get_option("{$size}_size_w"), get_option("{$size}_size_h"), $crop );
382-
383-                       if ( $resized )
384-                               $meta['sizes'][$size] = $resized;
385-                       else
386-                               unset($meta['sizes'][$size]);
387+                       $_sizes[ $size ] = array( 'width' => get_option("{$size}_size_w"), 'height' => get_option("{$size}_size_h"), 'crop' => $crop );
388                }
389+
390+               $meta['sizes'] = $img->multi_resize( $_sizes );
391        }
392 
393+       unset( $img );
394+
395        if ( $success ) {
396-               wp_update_attachment_metadata($post_id, $meta);
397+               wp_update_attachment_metadata( $post_id, $meta );
398                update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes);
399 
400                if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) {
401@@ -612,11 +724,9 @@ function wp_save_image($post_id) {
402 
403        if ( $delete ) {
404                $delpath = apply_filters('wp_delete_file', $new_path);
405-               @unlink($delpath);
406+               @unlink( $delpath );
407        }
408 
409-       imagedestroy($img);
410-
411        $return->msg = esc_js( __('Image saved') );
412        return $return;
413 }
414diff --git wp-admin/includes/image.php wp-admin/includes/image.php
415index c7638cd..2a5123e 100644
416--- wp-admin/includes/image.php
417+++ wp-admin/includes/image.php
418@@ -22,61 +22,33 @@
419  * @param string $dst_file Optional. The destination file to write to.
420  * @return string|WP_Error|false New filepath on success, WP_Error or false on failure.
421  */
422-function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
423-       if ( is_numeric( $src ) ) { // Handle int as attachment ID
424-               $src_file = get_attached_file( $src );
425+function wp_crop_image( $src_file, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
426+       if ( is_numeric( $src_file ) ) { // Handle int as attachment ID
427+               $src_file = get_attached_file( $src_file );
428                if ( ! file_exists( $src_file ) ) {
429                        // If the file doesn't exist, attempt a url fopen on the src link.
430                        // This can occur with certain file replication plugins.
431-                       $post = get_post( $src );
432-                       $image_type = $post->post_mime_type;
433-                       $src = load_image_to_edit( $src, $post->post_mime_type, 'full' );
434-               } else {
435-                       $size = @getimagesize( $src_file );
436-                       $image_type = ( $size ) ? $size['mime'] : '';
437-                       $src = wp_load_image( $src_file );
438+                       $src_file = _load_image_to_edit_path( $src_file, 'full' );
439                }
440-       } else {
441-               $size = @getimagesize( $src );
442-               $image_type = ( $size ) ? $size['mime'] : '';
443-               $src = wp_load_image( $src );
444-       }
445-
446-       if ( ! is_resource( $src ) )
447-               return new WP_Error( 'error_loading_image', $src, $src_file );
448-
449-       $dst = wp_imagecreatetruecolor( $dst_w, $dst_h );
450-
451-       if ( $src_abs ) {
452-               $src_w -= $src_x;
453-               $src_h -= $src_y;
454        }
455 
456-       if ( function_exists( 'imageantialias' ) )
457-               imageantialias( $dst, true );
458-
459-       imagecopyresampled( $dst, $src, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
460+       $editor = WP_Image_Editor::get_instance( $src_file );
461+       $src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
462 
463-       imagedestroy( $src ); // Free up memory
464+       if ( is_wp_error( $src ) )
465+               return $src;
466 
467        if ( ! $dst_file )
468                $dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file );
469 
470-       if ( 'image/png' != $image_type )
471-               $dst_file = preg_replace( '/\\.[^\\.]+$/', '.jpg', $dst_file );
472-
473        // The directory containing the original file may no longer exist when
474        // using a replication plugin.
475        wp_mkdir_p( dirname( $dst_file ) );
476 
477        $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), basename( $dst_file ) );
478 
479-       if ( 'image/png' == $image_type && imagepng( $dst, $dst_file ) )
480-               return $dst_file;
481-       elseif ( imagejpeg( $dst, $dst_file, apply_filters( 'jpeg_quality', 90, 'wp_crop_image' ) ) )
482-               return $dst_file;
483-       else
484-               return false;
485+       $result = $editor->save( $dst_file );
486+       return $dst_file;
487 }
488 
489 /**
490@@ -121,11 +93,8 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) {
491 
492                $sizes = apply_filters( 'intermediate_image_sizes_advanced', $sizes );
493 
494-               foreach ($sizes as $size => $size_data ) {
495-                       $resized = image_make_intermediate_size( $file, $size_data['width'], $size_data['height'], $size_data['crop'] );
496-                       if ( $resized )
497-                               $metadata['sizes'][$size] = $resized;
498-               }
499+               $editor = WP_Image_Editor::get_instance( $file );
500+               $metadata['sizes'] = $editor->multi_resize( $sizes );
501 
502                // fetch additional metadata from exif/iptc
503                $image_meta = wp_read_image_metadata( $file );
504diff --git wp-includes/class-wp-image-editor-gd.php wp-includes/class-wp-image-editor-gd.php
505new file mode 100644
506index 0000000..dcfdaae
507--- /dev/null
508+++ wp-includes/class-wp-image-editor-gd.php
509@@ -0,0 +1,336 @@
510+<?php
511+
512+class WP_Image_Editor_GD extends WP_Image_Editor {
513+       protected $image = false; // GD Resource
514+
515+       function __destruct() {
516+               if ( $this->image ) {
517+                       // we don't need the original in memory anymore
518+                       imagedestroy( $this->image );
519+               }
520+       }
521+
522+       /**
523+        * Checks to see if GD is available.
524+        *
525+        * @since 3.5
526+        * @access protected
527+        *
528+        * @return boolean
529+        */
530+       public static function test() {
531+               if ( ! extension_loaded('gd') || ! function_exists('gd_info') )
532+                       return false;
533+
534+               return true;
535+       }
536+
537+       /**
538+        * Loads image from $this->file into GD Resource
539+        *
540+        * @since 3.5
541+        * @access protected
542+        *
543+        * @return boolean|\WP_Error
544+        */
545+       protected function load() {
546+               if ( $this->image )
547+                       return true;
548+
549+               if ( ! file_exists( $this->file ) )
550+                       return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
551+
552+               // Set artificially high because GD uses uncompressed images in memory
553+               @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
554+               $this->image = @imagecreatefromstring( file_get_contents( $this->file ) );
555+
556+               if ( ! is_resource( $this->image ) )
557+                       return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file );
558+
559+               $size = @getimagesize( $this->file );
560+               if ( ! $size )
561+                       return new WP_Error( 'invalid_image', __('Could not read image size.'), $this->file );
562+
563+               $this->update_size( $size[0], $size[1] );
564+               $this->mime_type = $size['mime'];
565+
566+               return true;
567+       }
568+
569+       /**
570+        * Sets or updates current image size
571+        *
572+        * @since 3.5.0
573+        * @access protected
574+        *
575+        * @param int $width
576+        * @param int $height
577+        */
578+       protected function update_size( $width = false, $height = false ) {
579+               if ( ! $width )
580+                       $width = imagesx( $this->image );
581+
582+               if ( ! $height )
583+                       $height = imagesy( $this->image );
584+
585+               return parent::update_size( $width, $height );
586+       }
587+
588+       /**
589+        * Checks to see if editor supports mime-type specified
590+        *
591+        * @since 3.5.0
592+        * @access public
593+        *
594+        * @param string $mime_type
595+        * @return boolean
596+        */
597+       public static function supports_mime_type( $mime_type ) {
598+               $allowed_mime_types = array( 'image/gif', 'image/png', 'image/jpeg' );
599+
600+               return in_array( $mime_type, $allowed_mime_types );
601+       }
602+
603+       /**
604+        * Resizes current image.
605+        * Wrapper around _resize, since _resize returns a GD Resource
606+        *
607+        * @param int $max_w
608+        * @param int $max_h
609+        * @param boolean $crop
610+        * @return boolean|WP_Error
611+        */
612+       public function resize( $max_w, $max_h, $crop = false ) {
613+               if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
614+                       return true;
615+
616+               $resized = $this->_resize( $max_w, $max_h, $crop );
617+
618+               if ( is_resource( $resized ) ) {
619+                       imagedestroy( $this->image );
620+                       $this->image = $resized;
621+                       return true;
622+
623+               } elseif ( is_wp_error( $resized ) )
624+                       return $resized;
625+
626+               return new WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file );
627+       }
628+
629+       protected function _resize( $max_w, $max_h, $crop = false ) {
630+               $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
631+               if ( ! $dims ) {
632+                       return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions'), $this->file );
633+               }
634+               list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
635+
636+               $resized = wp_imagecreatetruecolor( $dst_w, $dst_h );
637+               imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
638+
639+               if ( is_resource( $resized ) ) {
640+                       $this->update_size( $dst_w, $dst_h );
641+                       return $resized;
642+               }
643+
644+               return WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file );
645+       }
646+
647+       /**
648+        * Processes current image and saves to disk
649+        * multiple sizes from single source.
650+        *
651+        * @param array $sizes { {width, height}, ... }
652+        * @return array
653+        */
654+       public function multi_resize( $sizes ) {
655+               $metadata = array();
656+               $orig_size = $this->size;
657+
658+               foreach ( $sizes as $size => $size_data ) {
659+                       $image = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
660+
661+                       if( ! is_wp_error( $image ) ) {
662+                               $resized = $this->_save( $image );
663+
664+                               imagedestroy( $image );
665+                               unset( $resized['path'] );
666+
667+                               if ( ! is_wp_error( $resized ) && $resized )
668+                                       $metadata[$size] = $resized;
669+                       }
670+
671+                       $this->size = $orig_size;
672+               }
673+
674+               return $metadata;
675+       }
676+
677+       /**
678+        * Crops Image.
679+        *
680+        * @param float $x
681+        * @param float $y
682+        * @param float $w
683+        * @param float $h
684+        * @return boolean
685+        */
686+       public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
687+               // If destination width/height isn't specified, use same as
688+               // width/height from source.
689+               if ( ! $dst_w )
690+                       $dst_w = $src_w;
691+               if ( ! $dst_h )
692+                       $dst_h = $src_h;
693+
694+               $dst = wp_imagecreatetruecolor( $dst_w, $dst_h );
695+
696+               if ( $src_abs ) {
697+                       $src_w -= $src_x;
698+                       $src_h -= $src_y;
699+               }
700+
701+               if ( function_exists( 'imageantialias' ) )
702+                       imageantialias( $dst, true );
703+
704+               imagecopyresampled( $dst, $this->image, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
705+
706+               if ( is_resource( $dst ) ) {
707+                       imagedestroy( $this->image );
708+                       $this->image = $dst;
709+                       $this->update_size( $dst_w, $dst_h );
710+                       return true;
711+               }
712+
713+               return WP_Error( 'image_crop_error', __('Image crop failed.'), $this->file );
714+       }
715+
716+       /**
717+        * Rotates current image counter-clockwise by $angle.
718+        * Ported from image-edit.php
719+        *
720+        * @since 3.5.0
721+        * @access public
722+        *
723+        * @param float $angle
724+        * @return boolean|WP_Error
725+        */
726+       public function rotate( $angle ) {
727+               if ( function_exists('imagerotate') ) {
728+                       $rotated = imagerotate( $this->image, $angle, 0 );
729+
730+                       if ( is_resource( $rotated ) ) {
731+                               imagedestroy( $this->image );
732+                               $this->image = $rotated;
733+                               $this->update_size();
734+                               return true;
735+                       }
736+               }
737+               return WP_Error( 'image_rotate_error', __('Image rotate failed.'), $this->file );
738+       }
739+
740+       /**
741+        * Flips current image
742+        *
743+        * @param boolean $horz Horizonal Flip
744+        * @param boolean $vert Vertical Flip
745+        * @returns boolean|WP_Error
746+        */
747+       public function flip( $horz, $vert ) {
748+               $w = $this->size['width'];
749+               $h = $this->size['height'];
750+               $dst = wp_imagecreatetruecolor( $w, $h );
751+
752+               if ( is_resource( $dst ) ) {
753+                       $sx = $vert ? ($w - 1) : 0;
754+                       $sy = $horz ? ($h - 1) : 0;
755+                       $sw = $vert ? -$w : $w;
756+                       $sh = $horz ? -$h : $h;
757+
758+                       if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) {
759+                               imagedestroy( $this->image );
760+                               $this->image = $dst;
761+                               return true;
762+                       }
763+               }
764+               return WP_Error( 'image_flip_error', __('Image flip failed.'), $this->file );
765+       }
766+
767+       /**
768+        * Saves current in-memory image to file
769+        *
770+        * @param string $destfilename
771+        * @param string $mime_type
772+        * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
773+        */
774+       public function save( $filename = null, $mime_type = null ) {
775+               $saved = $this->_save( $this->image, $filename, $mime_type );
776+
777+               if ( ! is_wp_error( $saved ) ) {
778+                       $this->file = $saved['path'];
779+                       $this->mime_type = $saved['mime-type'];
780+               }
781+
782+               return $saved;
783+       }
784+
785+       protected function _save( $image, $filename = null, $mime_type = null ) {
786+               list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
787+
788+               if ( ! $filename )
789+                       $filename = $this->generate_filename( null, null, $extension );
790+
791+               if ( 'image/gif' == $mime_type ) {
792+                       if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) )
793+                               return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
794+               }
795+               elseif ( 'image/png' == $mime_type ) {
796+                       // convert from full colors to index colors, like original PNG.
797+                       if ( function_exists('imageistruecolor') && ! imageistruecolor( $image ) )
798+                               imagetruecolortopalette( $image, false, imagecolorstotal( $image ) );
799+
800+                       if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) )
801+                               return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
802+               }
803+               elseif ( 'image/jpeg' == $mime_type ) {
804+                       if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, apply_filters( 'jpeg_quality', $this->quality, 'image_resize' ) ) ) )
805+                               return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
806+               }
807+               else {
808+                       return new WP_Error( 'image_save_error', __('Image Editor Save Failed') );
809+               }
810+
811+               // Set correct file permissions
812+               $stat = stat( dirname( $filename ) );
813+               $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
814+               @ chmod( $filename, $perms );
815+
816+               return array(
817+                       'path' => $filename,
818+                       'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
819+                       'width' => $this->size['width'],
820+                       'height' => $this->size['height'],
821+                       'mime-type'=> $mime_type,
822+               );
823+       }
824+
825+       /**
826+        * Returns stream of current image
827+        *
828+        * @param string $mime_type
829+        */
830+       public function stream( $mime_type = null ) {
831+               list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
832+
833+               switch ( $mime_type ) {
834+                       case 'image/png':
835+                               header( 'Content-Type: image/png' );
836+                               return imagepng( $this->image );
837+                       case 'image/gif':
838+                               header( 'Content-Type: image/gif' );
839+                               return imagegif( $this->image );
840+                       default:
841+                               header( 'Content-Type: image/jpeg' );
842+                               return imagejpeg( $this->image, null, $this->quality );
843+               }
844+       }
845+}
846\ No newline at end of file
847diff --git wp-includes/class-wp-image-editor-imagick.php wp-includes/class-wp-image-editor-imagick.php
848new file mode 100644
849index 0000000..cd69608
850--- /dev/null
851+++ wp-includes/class-wp-image-editor-imagick.php
852@@ -0,0 +1,389 @@
853+<?php
854+
855+class WP_Image_Editor_Imagick extends WP_Image_Editor {
856+       protected $image = null; // Imagick Object
857+
858+       function __destruct() {
859+               if ( $this->image ) {
860+                       // we don't need the original in memory anymore
861+                       $this->image->clear();
862+                       $this->image->destroy();
863+               }
864+       }
865+
866+       /**
867+        * Checks to see if current environment supports Imagick
868+        *
869+        * @since 3.5.0
870+        * @access protected
871+        *
872+        * @return boolean
873+        */
874+       public static function test() {
875+               if ( ! extension_loaded( 'imagick' ) )
876+                       return false;
877+
878+               return true;
879+       }
880+
881+       /**
882+        * Load image in $this->file into new Imagick Object
883+        *
884+        * @since 3.5.0
885+        * @access protected
886+        *
887+        * @return boolean|WP_Error True if loaded; WP_Error on failure.
888+        */
889+       protected function load() {
890+               if ( $this->image )
891+                       return true;
892+
893+               if ( ! file_exists( $this->file ) )
894+                       return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
895+
896+               try {
897+                       $this->image = new Imagick( $this->file );
898+
899+                       if( ! $this->image->valid() )
900+                               return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
901+
902+                       // Select the first frame to handle animated GIFs properly
903+                       $this->image->setIteratorIndex(0);
904+                       $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
905+               }
906+               catch ( Exception $e ) {
907+                       return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
908+               }
909+
910+               $updated_size = $this->update_size();
911+               if ( is_wp_error( $updated_size ) )
912+                               return $updated_size;
913+
914+               return $this->set_quality();
915+       }
916+
917+       /**
918+        * Sets Image Compression quality on a 1-100% scale.
919+        *
920+        * @since 3.5.0
921+        * @access public
922+        *
923+        * @param int $quality Compression Quality. Range: [1,100]
924+        * @return boolean|WP_Error
925+        */
926+       public function set_quality( $quality = null ) {
927+               if ( !$quality )
928+                       $quality = $this->quality;
929+
930+               try {
931+                       if( 'image/jpeg' == $this->mime_type ) {
932+                               $this->image->setImageCompressionQuality( apply_filters( 'jpeg_quality', $quality, 'image_resize' ) );
933+                               $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
934+                       }
935+                       else {
936+                               $this->image->setImageCompressionQuality( $quality );
937+                       }
938+               }
939+               catch ( Exception $e ) {
940+                       return new WP_Error( 'image_quality_error', $e->getMessage() );
941+               }
942+
943+               return parent::set_quality( $quality );
944+       }
945+
946+       /**
947+        * Sets or updates current image size
948+        *
949+        * @since 3.5.0
950+        * @access protected
951+        *
952+        * @param int $width
953+        * @param int $height
954+        */
955+       protected function update_size( $width = null, $height = null ) {
956+               $size = null;
957+               if ( !$width || !$height ) {
958+                       try {
959+                               $size = $this->image->getImageGeometry();
960+                       }
961+                       catch ( Exception $e ) {
962+                               return new WP_Error( 'invalid_image', __('Could not read image size'), $this->file );
963+                       }
964+               }
965+
966+               if ( ! $width )
967+                       $width = $size['width'];
968+
969+               if ( ! $height )
970+                       $height = $size['height'];
971+
972+               return parent::update_size( $width, $height );
973+       }
974+
975+       /**
976+        * Checks to see if editor supports mime-type specified
977+        *
978+        * @since 3.5.0
979+        * @access public
980+        *
981+        * @param string $mime_type
982+        * @return boolean
983+        */
984+       public static function supports_mime_type( $mime_type = null ) {
985+               if ( ! $mime_type )
986+                       return false;
987+
988+               $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
989+
990+               try {
991+                       return ( (bool) Imagick::queryFormats( $imagick_extension ) );
992+               }
993+               catch ( Exception $e ) {
994+                       return false;
995+               }
996+       }
997+
998+       /**
999+        * Resizes current image.
1000+        *
1001+        * @param int $max_w
1002+        * @param int $max_h
1003+        * @param boolean $crop
1004+        * @return boolean|WP_Error
1005+        */
1006+       public function resize( $max_w, $max_h, $crop = false ) {
1007+               if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
1008+                       return true;
1009+
1010+               $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
1011+               if ( ! $dims )
1012+                       return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
1013+               list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
1014+
1015+               if ( $crop ) {
1016+                       return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
1017+               }
1018+
1019+               try {
1020+                       /**
1021+                        * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick.
1022+                        * $this->image->thumbnailImage( $dst_w, $dst_h );
1023+                        */
1024+                       $this->image->scaleImage( $dst_w, $dst_h );
1025+               }
1026+               catch ( Exception $e ) {
1027+                       return new WP_Error( 'image_resize_error', $e->getMessage() );
1028+               }
1029+
1030+               return $this->update_size( $dst_w, $dst_h );
1031+       }
1032+
1033+       /**
1034+        * Processes current image and saves to disk
1035+        * multiple sizes from single source.
1036+        *
1037+        * @param array $sizes
1038+        * @return array
1039+        */
1040+       public function multi_resize( $sizes ) {
1041+               $metadata = array();
1042+               $orig_size = $this->size;
1043+               $orig_image = $this->image->getImage();
1044+
1045+               foreach ( $sizes as $size => $size_data ) {
1046+                       if ( ! $this->image )
1047+                               $this->image = $orig_image->getImage();
1048+
1049+                       $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
1050+
1051+                       if( ! is_wp_error( $resize_result ) ) {
1052+                               $resized = $this->_save( $this->image );
1053+
1054+                               $this->image->clear();
1055+                               $this->image->destroy();
1056+                               $this->image = null;
1057+                               unset( $resized['path'] );
1058+
1059+                               if ( ! is_wp_error( $resized ) && $resized )
1060+                                       $metadata[$size] = $resized;
1061+                       }
1062+
1063+                       $this->size = $orig_size;
1064+               }
1065+
1066+               $this->image = $orig_image;
1067+
1068+               return $metadata;
1069+       }
1070+
1071+       /**
1072+        * Crops image.
1073+        *
1074+        * @param float $x
1075+        * @param float $y
1076+        * @param float $w
1077+        * @param float $h
1078+        * @return boolean
1079+        */
1080+       public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
1081+               // Not sure this is compatible.
1082+               if ( $src_abs ) {
1083+                       $src_w -= $src_x;
1084+                       $src_h -= $src_y;
1085+               }
1086+
1087+               try {
1088+                       $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
1089+                       $this->image->setImagePage( $src_w, $src_h, 0, 0);
1090+
1091+                       if ( $dst_w || $dst_h ) {
1092+                               // If destination width/height isn't specified, use same as
1093+                               // width/height from source.
1094+                               if ( ! $dst_w )
1095+                                       $dst_w = $src_w;
1096+                               if ( ! $dst_h )
1097+                                       $dst_h = $src_h;
1098+
1099+                               $this->image->scaleImage( $dst_w, $dst_h );
1100+                               return $this->update_size( $dst_w, $dst_h );
1101+                       }
1102+               }
1103+               catch ( Exception $e ) {
1104+                       return new WP_Error( 'image_crop_error', $e->getMessage() );
1105+               }
1106+               return $this->update_size( $src_w, $src_h );
1107+       }
1108+
1109+       /**
1110+        * Rotates current image counter-clockwise by $angle.
1111+        *
1112+        * @since 3.5.0
1113+        * @access public
1114+        *
1115+        * @param float $angle
1116+        * @return boolean|WP_Error
1117+        */
1118+       public function rotate( $angle ) {
1119+               /**
1120+                * $angle is 360-$angle because Imagick rotates clockwise
1121+                * (GD rotates counter-clockwise)
1122+                */
1123+               try {
1124+                       $this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
1125+               }
1126+               catch ( Exception $e ) {
1127+                       return new WP_Error( 'image_rotate_error', $e->getMessage() );
1128+               }
1129+               return $this->update_size();
1130+       }
1131+
1132+       /**
1133+        * Flips current image
1134+        *
1135+        * @since 3.5.0
1136+        * @access public
1137+        *
1138+        * @param boolean $horz Horizontal Flip
1139+        * @param boolean $vert Vertical Flip
1140+        * @returns boolean
1141+        */
1142+       public function flip( $horz, $vert ) {
1143+               try {
1144+                       if ( $horz )
1145+                               $this->image->flipImage();
1146+
1147+                       if ( $vert )
1148+                               $this->image->flopImage();
1149+               }
1150+               catch ( Exception $e ) {
1151+                       return new WP_Error( 'image_flip_error', $e->getMessage() );
1152+               }
1153+               return true;
1154+       }
1155+
1156+       /**
1157+        * Saves current image to file
1158+        *
1159+        * @param string $destfilename
1160+        * @param string $mime_type
1161+        * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
1162+        */
1163+       public function save( $destfilename = null, $mime_type = null ) {
1164+               $saved = $this->_save( $this->image, $destfilename, $mime_type );
1165+
1166+               if ( ! is_wp_error( $saved ) ) {
1167+                       $this->file = $saved['path'];
1168+                       $this->mime_type = $saved['mime-type'];
1169+
1170+                       try {
1171+                               $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
1172+                       }
1173+                       catch ( Exception $e ) {
1174+                               return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
1175+                       }
1176+               }
1177+
1178+               return $saved;
1179+       }
1180+
1181+       protected function _save( $image, $filename = null, $mime_type = null ) {
1182+               list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
1183+
1184+               if ( ! $filename )
1185+                       $filename = $this->generate_filename( null, null, $extension );
1186+
1187+               try {
1188+                       // Store initial Format
1189+                       $orig_format = $this->image->getImageFormat();
1190+
1191+                       $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
1192+                       $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
1193+
1194+                       // Reset original Format
1195+                       $this->image->setImageFormat( $orig_format );
1196+               }
1197+               catch ( Exception $e ) {
1198+                       return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
1199+               }
1200+
1201+               // Set correct file permissions
1202+               $stat = stat( dirname( $filename ) );
1203+               $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
1204+               @ chmod( $filename, $perms );
1205+
1206+               return array(
1207+                       'path' => $filename,
1208+                       'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
1209+                       'width' => $this->size['width'],
1210+                       'height' => $this->size['height'],
1211+                       'mime-type' => $mime_type,
1212+               );
1213+       }
1214+
1215+       /**
1216+        * Streams current image to browser
1217+        *
1218+        * @param string $mime_type
1219+        * @return boolean|WP_Error
1220+        */
1221+       public function stream( $mime_type = null ) {
1222+               list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
1223+
1224+               try {
1225+                       // Temporarily change format for stream
1226+                       $this->image->setImageFormat( strtoupper( $extension ) );
1227+
1228+                       // Output stream of image content
1229+                       header( "Content-Type: $mime_type" );
1230+                       print $this->image->getImageBlob();
1231+
1232+                       // Reset Image to original Format
1233+                       $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
1234+               }
1235+               catch ( Exception $e ) {
1236+                       return new WP_Error( 'image_stream_error', $e->getMessage() );
1237+               }
1238+
1239+               return true;
1240+       }
1241+}
1242\ No newline at end of file
1243diff --git wp-includes/class-wp-image-editor.php wp-includes/class-wp-image-editor.php
1244new file mode 100644
1245index 0000000..515b45e
1246--- /dev/null
1247+++ wp-includes/class-wp-image-editor.php
1248@@ -0,0 +1,313 @@
1249+<?php
1250+
1251+abstract class WP_Image_Editor {
1252+       protected $file = null;
1253+       protected $size = null;
1254+       protected $mime_type  = null;
1255+       protected $default_mime_type = 'image/jpeg';
1256+       protected $quality = 90;
1257+       private static $implementation;
1258+
1259+       protected function __construct( $filename ) {
1260+               $this->file = $filename;
1261+       }
1262+
1263+       /**
1264+        * Returns a WP_Image_Editor instance and loads file into it.
1265+        *
1266+        * @since 3.5.0
1267+        * @access public
1268+        *
1269+        * @param string $path Path to File to Load
1270+        * @return WP_Image_Editor|WP_Error|boolean
1271+        */
1272+       public final static function get_instance( $path = null ) {
1273+               $implementation = apply_filters( 'image_editor_class', self::choose_implementation(), $path );
1274+
1275+               if ( $implementation ) {
1276+                       $editor = new $implementation( $path );
1277+                       $loaded = $editor->load();
1278+
1279+                       if ( is_wp_error ( $loaded ) )
1280+                               return $loaded;
1281+
1282+                       return $editor;
1283+               }
1284+
1285+               return false;
1286+       }
1287+
1288+       /**
1289+        * Tests which editors are capable of supporting the request.
1290+        *
1291+        * @since 3.5.0
1292+        * @access private
1293+        *
1294+        * @return string|bool Class name for the first editor that claims to support the request. False if no editor claims to support the request.
1295+        */
1296+       private final static function choose_implementation() {
1297+
1298+               if ( null === self::$implementation ) {
1299+                       $request_order = apply_filters( 'wp_editors', array( 'imagick', 'gd' ) );
1300+
1301+                       // Loop over each editor on each request looking for one which will serve this request's needs
1302+                       foreach ( $request_order as $editor ) {
1303+                               $class = 'WP_Image_Editor_' . $editor;
1304+
1305+                               // Check to see if this editor is a possibility, calls the editor statically
1306+                               if ( ! call_user_func( array( $class, 'test' ) ) )
1307+                                       continue;
1308+
1309+                               self::$implementation = $class;
1310+                               break;
1311+                       }
1312+               }
1313+               return self::$implementation;
1314+       }
1315+
1316+       abstract public static function test(); // returns bool
1317+       abstract protected function load(); // returns bool|WP_Error
1318+       abstract public static function supports_mime_type( $mime_type ); // returns bool
1319+       abstract public function resize( $max_w, $max_h, $crop = false );
1320+       abstract public function multi_resize( $sizes );
1321+       abstract public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false );
1322+       abstract public function rotate( $angle );
1323+       abstract public function flip( $horz, $vert );
1324+       abstract public function save( $destfilename = null, $mime_type = null );
1325+       abstract public function stream( $mime_type = null );
1326+
1327+       /**
1328+        * Gets dimensions of image
1329+        *
1330+        * @since 3.5.0
1331+        * @access public
1332+        *
1333+        * @return array {'width'=>int, 'height'=>int}
1334+        */
1335+       public function get_size() {
1336+               return $this->size;
1337+       }
1338+
1339+       /**
1340+        * Sets current image size
1341+        *
1342+        * @since 3.5.0
1343+        * @access protected
1344+        *
1345+        * @param int $width
1346+        * @param int $height
1347+        */
1348+       protected function update_size( $width = null, $height = null ) {
1349+               $this->size = array(
1350+                       'width' => $width,
1351+                       'height' => $height
1352+               );
1353+               return true;
1354+       }
1355+
1356+       /**
1357+        * Sets Image Compression quality on a 1-100% scale.
1358+        *
1359+        * @since 3.5.0
1360+        * @access public
1361+        *
1362+        * @param int $quality Compression Quality. Range: [1,100]
1363+        * @return boolean
1364+        */
1365+       public function set_quality( $quality ) {
1366+               $this->quality = apply_filters( 'wp_editor_set_quality', $quality );
1367+
1368+               return ( (bool) $this->quality );
1369+       }
1370+
1371+       /**
1372+        * Returns preferred mime-type and extension based on provided
1373+        * file's extension and mime, or current file's extension and mime.
1374+        *
1375+        * Will default to $this->default_mime_type if requested is not supported.
1376+        *
1377+        * Provides corrected filename only if filename is provided.
1378+        *
1379+        * @since 3.5.0
1380+        * @access protected
1381+        *
1382+        * @param string $filename
1383+        * @param type $mime_type
1384+        * @return array { filename|null, extension, mime-type }
1385+        */
1386+       protected function get_output_format( $filename = null, $mime_type = null ) {
1387+               $new_ext = $file_ext = null;
1388+               $file_mime = null;
1389+
1390+               // By default, assume specified type takes priority
1391+               if ( $mime_type ) {
1392+                       $new_ext = $this->get_extension( $mime_type );
1393+               }
1394+
1395+               if ( $filename ) {
1396+                       $file_ext = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
1397+                       $file_mime = $this->get_mime_type( $file_ext );
1398+               }
1399+               else {
1400+                       // If no file specified, grab editor's current extension and mime-type.
1401+                       $file_ext = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
1402+                       $file_mime = $this->mime_type;
1403+               }
1404+
1405+               // Check to see if specified mime-type is the same as type implied by
1406+               // file extension.  If so, prefer extension from file.
1407+               if ( ! $mime_type || ( $file_mime == $mime_type ) ) {
1408+                       $mime_type = $file_mime;
1409+                       $new_ext = $file_ext;
1410+               }
1411+
1412+               // Double-check that the mime-type selected is supported by the editor.
1413+               // If not, choose a default instead.
1414+               if ( ! $this->supports_mime_type( $mime_type ) ) {
1415+                       $mime_type = apply_filters( 'image_editor_default_mime_type', $this->default_mime_type );
1416+                       $new_ext = $this->get_extension( $mime_type );
1417+               }
1418+
1419+               if ( $filename ) {
1420+                       $ext = '';
1421+                       $info = pathinfo( $filename );
1422+                       $dir  = $info['dirname'];
1423+
1424+                       if( isset( $info['extension'] ) )
1425+                               $ext = $info['extension'];
1426+
1427+                       $filename = trailingslashit( $dir ) . wp_basename( $filename, ".$ext" ) . ".{$new_ext}";
1428+               }
1429+
1430+               return array( $filename, $new_ext, $mime_type );
1431+       }
1432+
1433+       /**
1434+        * Builds an output filename based on current file, and adding proper suffix
1435+        *
1436+        * @since 3.5.0
1437+        * @access public
1438+        *
1439+        * @param string $suffix
1440+        * @param string $dest_path
1441+        * @param string $extension
1442+        * @return string filename
1443+        */
1444+       public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
1445+               // $suffix will be appended to the destination filename, just before the extension
1446+               if ( ! $suffix )
1447+                       $suffix = $this->get_suffix();
1448+
1449+               $info = pathinfo( $this->file );
1450+               $dir  = $info['dirname'];
1451+               $ext  = $info['extension'];
1452+
1453+               $name = wp_basename( $this->file, ".$ext" );
1454+               $new_ext = strtolower( $extension ? $extension : $ext );
1455+
1456+               if ( ! is_null( $dest_path ) && $_dest_path = realpath( $dest_path ) )
1457+                       $dir = $_dest_path;
1458+
1459+               return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
1460+       }
1461+
1462+       /**
1463+        * Builds and returns proper suffix for file based on height and width.
1464+        *
1465+        * @since 3.5.0
1466+        * @access public
1467+        *
1468+        * @return string suffix
1469+        */
1470+       public function get_suffix() {
1471+               if ( ! $this->get_size() )
1472+                       return false;
1473+
1474+               return "{$this->size['width']}x{$this->size['height']}";
1475+       }
1476+
1477+       /**
1478+        * Either calls editor's save function or handles file as a stream.
1479+        *
1480+        * @since 3.5.0
1481+        * @access protected
1482+        *
1483+        * @param string|stream $filename
1484+        * @param callable $function
1485+        * @param array $arguments
1486+        * @return boolean
1487+        */
1488+       protected function make_image( $filename, $function, $arguments ) {
1489+               $dst_file = $filename;
1490+
1491+               if ( $stream = wp_is_stream( $filename ) ) {
1492+                       $filename = null;
1493+                       ob_start();
1494+               }
1495+
1496+               $result = call_user_func_array( $function, $arguments );
1497+
1498+               if( $result && $stream ) {
1499+                       $contents = ob_get_contents();
1500+
1501+                       $fp = fopen( $dst_file, 'w' );
1502+
1503+                       if( ! $fp )
1504+                               return false;
1505+
1506+                       fwrite( $fp, $contents );
1507+                       fclose( $fp );
1508+               }
1509+
1510+               if( $stream ) {
1511+                       ob_end_clean();
1512+               }
1513+
1514+               return $result;
1515+       }
1516+
1517+       /**
1518+        * Returns first matched mime-type from extension,
1519+        * as mapped from wp_get_mime_types()
1520+        *
1521+        * @since 3.5.0
1522+        * @access protected
1523+        *
1524+        * @param string $extension
1525+        * @return string|boolean
1526+        */
1527+       protected static function get_mime_type( $extension = null ) {
1528+               if ( ! $extension )
1529+                       return false;
1530+
1531+               $mime_types = wp_get_mime_types();
1532+               $extensions = array_keys( $mime_types );
1533+
1534+               foreach( $extensions as $_extension ) {
1535+                       if( preg_match("/{$extension}/i", $_extension ) ) {
1536+                               return $mime_types[ $_extension ];
1537+                       }
1538+               }
1539+
1540+               return false;
1541+       }
1542+
1543+       /**
1544+        * Returns first matched extension from Mime-type,
1545+        * as mapped from wp_get_mime_types()
1546+        *
1547+        * @since 3.5.0
1548+        * @access protected
1549+        *
1550+        * @param string $mime_type
1551+        * @return string|boolean
1552+        */
1553+       protected static function get_extension( $mime_type = null ) {
1554+               $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types() ) );
1555+
1556+               if ( empty( $extensions[0] ) )
1557+                       return false;
1558+
1559+               return $extensions[0];
1560+       }
1561+}
1562\ No newline at end of file
1563diff --git wp-includes/deprecated.php wp-includes/deprecated.php
1564index f589017..a9744e8 100644
1565--- wp-includes/deprecated.php
1566+++ wp-includes/deprecated.php
1567@@ -3206,6 +3206,83 @@ function _get_post_ancestors( &$post ) {
1568 }
1569 
1570 /**
1571+ * Load an image from a string, if PHP supports it.
1572+ *
1573+ * @since 2.1.0
1574+ * @deprecated 3.5.0
1575+ * @see WP_Image_Editor
1576+ *
1577+ * @param string $file Filename of the image to load.
1578+ * @return resource The resulting image resource on success, Error string on failure.
1579+ */
1580+function wp_load_image( $file ) {
1581+       _deprecated_function( __FUNCTION__, '3.5', 'WP_Image_Editor' );
1582+
1583+       if ( is_numeric( $file ) )
1584+               $file = get_attached_file( $file );
1585+
1586+       if ( ! file_exists( $file ) )
1587+               return sprintf(__('File &#8220;%s&#8221; doesn&#8217;t exist?'), $file);
1588+
1589+       if ( ! function_exists('imagecreatefromstring') )
1590+               return __('The GD image library is not installed.');
1591+
1592+       // Set artificially high because GD uses uncompressed images in memory
1593+       @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
1594+       $image = imagecreatefromstring( file_get_contents( $file ) );
1595+
1596+       if ( !is_resource( $image ) )
1597+               return sprintf(__('File &#8220;%s&#8221; is not an image.'), $file);
1598+
1599+       return $image;
1600+}
1601+
1602+/**
1603+ * Scale down an image to fit a particular size and save a new copy of the image.
1604+ *
1605+ * The PNG transparency will be preserved using the function, as well as the
1606+ * image type. If the file going in is PNG, then the resized image is going to
1607+ * be PNG. The only supported image types are PNG, GIF, and JPEG.
1608+ *
1609+ * Some functionality requires API to exist, so some PHP version may lose out
1610+ * support. This is not the fault of WordPress (where functionality is
1611+ * downgraded, not actual defects), but of your PHP version.
1612+ *
1613+ * @since 2.5.0
1614+ * @deprecated 3.5.0
1615+ * @see WP_Image_Editor
1616+ *
1617+ * @param string $file Image file path.
1618+ * @param int $max_w Maximum width to resize to.
1619+ * @param int $max_h Maximum height to resize to.
1620+ * @param bool $crop Optional. Whether to crop image or resize.
1621+ * @param string $suffix Optional. File suffix.
1622+ * @param string $dest_path Optional. New image file path.
1623+ * @param int $jpeg_quality Optional, default is 90. Image quality percentage.
1624+ * @return mixed WP_Error on failure. String with new destination path.
1625+ */
1626+function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 90 ) {
1627+       _deprecated_function( __FUNCTION__, '3.5', 'WP_Image_Editor' );
1628+
1629+       $editor = WP_Image_Editor::get_instance( $file );
1630+       if ( is_wp_error( $editor ) )
1631+               return $editor;
1632+       $editor->set_quality( $jpeg_quality );
1633+
1634+       $resized = $editor->resize( $max_w, $max_h, $crop );
1635+       if ( is_wp_error( $resized ) )
1636+               return $resized;
1637+
1638+       $dest_file = $editor->generate_filename( $suffix, $dest_path );
1639+       $saved = $editor->save( $dest_file );
1640+
1641+       if ( is_wp_error( $saved ) )
1642+               return $saved;
1643+
1644+       return $dest_file;
1645+}
1646+
1647+/**
1648  * Retrieve a single post, based on post ID.
1649  *
1650  * Has categories in 'post_category' property or key. Has tags in 'tags_input'
1651diff --git wp-includes/functions.php wp-includes/functions.php
1652index e51c155..104e89a 100644
1653--- wp-includes/functions.php
1654+++ wp-includes/functions.php
1655@@ -1295,9 +1295,21 @@ function wp_get_original_referer() {
1656  * @return bool Whether the path was created. True if path already exists.
1657  */
1658 function wp_mkdir_p( $target ) {
1659+       $wrapper = null;
1660+
1661+       // strip the protocol
1662+       if( wp_is_stream( $target ) ) {
1663+               list( $wrapper, $target ) = explode( '://', $target, 2 );
1664+       }
1665+
1666        // from php.net/mkdir user contributed notes
1667        $target = str_replace( '//', '/', $target );
1668 
1669+       // put the wrapper back on the target
1670+       if( $wrapper !== null ) {
1671+               $target = $wrapper . '://' . $target;
1672+       }
1673+
1674        // safe mode fails with a trailing slash under certain PHP versions.
1675        $target = rtrim($target, '/'); // Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
1676        if ( empty($target) )
1677@@ -3741,6 +3753,19 @@ function _device_can_upload() {
1678 }
1679 
1680 /**
1681+ * Test if a given path is a stream URL
1682+ *
1683+ * @param string $path The resource path or URL
1684+ * @return bool True if the path is a stream URL
1685+ */
1686+function wp_is_stream( $path ) {
1687+       $wrappers = stream_get_wrappers();
1688+       $wrappers_re = '(' . join('|', $wrappers) . ')';
1689+
1690+       return preg_match( "!^$wrappers_re://!", $path ) === 1;
1691+}
1692+
1693+/**
1694  * Test if the supplied date is valid for the Gregorian calendar
1695  *
1696  * @since 3.5.0
1697diff --git wp-includes/media.php wp-includes/media.php
1698index 662678a..2e13035 100644
1699--- wp-includes/media.php
1700+++ wp-includes/media.php
1701@@ -236,34 +236,6 @@ function get_image_tag($id, $alt, $title, $align, $size='medium') {
1702 }
1703 
1704 /**
1705- * Load an image from a string, if PHP supports it.
1706- *
1707- * @since 2.1.0
1708- *
1709- * @param string $file Filename of the image to load.
1710- * @return resource The resulting image resource on success, Error string on failure.
1711- */
1712-function wp_load_image( $file ) {
1713-       if ( is_numeric( $file ) )
1714-               $file = get_attached_file( $file );
1715-
1716-       if ( ! file_exists( $file ) )
1717-               return sprintf(__('File &#8220;%s&#8221; doesn&#8217;t exist?'), $file);
1718-
1719-       if ( ! function_exists('imagecreatefromstring') )
1720-               return __('The GD image library is not installed.');
1721-
1722-       // Set artificially high because GD uses uncompressed images in memory
1723-       @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
1724-       $image = imagecreatefromstring( file_get_contents( $file ) );
1725-
1726-       if ( !is_resource( $image ) )
1727-               return sprintf(__('File &#8220;%s&#8221; is not an image.'), $file);
1728-
1729-       return $image;
1730-}
1731-
1732-/**
1733  * Calculates the new dimensions for a downsampled image.
1734  *
1735  * If either width or height are empty, no constraint is applied on
1736@@ -393,92 +365,6 @@ function image_resize_dimensions($orig_w, $orig_h, $dest_w, $dest_h, $crop = fal
1737 }
1738 
1739 /**
1740- * Scale down an image to fit a particular size and save a new copy of the image.
1741- *
1742- * The PNG transparency will be preserved using the function, as well as the
1743- * image type. If the file going in is PNG, then the resized image is going to
1744- * be PNG. The only supported image types are PNG, GIF, and JPEG.
1745- *
1746- * Some functionality requires API to exist, so some PHP version may lose out
1747- * support. This is not the fault of WordPress (where functionality is
1748- * downgraded, not actual defects), but of your PHP version.
1749- *
1750- * @since 2.5.0
1751- *
1752- * @param string $file Image file path.
1753- * @param int $max_w Maximum width to resize to.
1754- * @param int $max_h Maximum height to resize to.
1755- * @param bool $crop Optional. Whether to crop image or resize.
1756- * @param string $suffix Optional. File suffix.
1757- * @param string $dest_path Optional. New image file path.
1758- * @param int $jpeg_quality Optional, default is 90. Image quality percentage.
1759- * @return mixed WP_Error on failure. String with new destination path.
1760- */
1761-function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 90 ) {
1762-
1763-       $image = wp_load_image( $file );
1764-       if ( !is_resource( $image ) )
1765-               return new WP_Error( 'error_loading_image', $image, $file );
1766-
1767-       $size = @getimagesize( $file );
1768-       if ( !$size )
1769-               return new WP_Error('invalid_image', __('Could not read image size'), $file);
1770-       list($orig_w, $orig_h, $orig_type) = $size;
1771-
1772-       $dims = image_resize_dimensions($orig_w, $orig_h, $max_w, $max_h, $crop);
1773-       if ( !$dims )
1774-               return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
1775-       list($dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) = $dims;
1776-
1777-       $newimage = wp_imagecreatetruecolor( $dst_w, $dst_h );
1778-
1779-       imagecopyresampled( $newimage, $image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1780-
1781-       // convert from full colors to index colors, like original PNG.
1782-       if ( IMAGETYPE_PNG == $orig_type && function_exists('imageistruecolor') && !imageistruecolor( $image ) )
1783-               imagetruecolortopalette( $newimage, false, imagecolorstotal( $image ) );
1784-
1785-       // we don't need the original in memory anymore
1786-       imagedestroy( $image );
1787-
1788-       // $suffix will be appended to the destination filename, just before the extension
1789-       if ( !$suffix )
1790-               $suffix = "{$dst_w}x{$dst_h}";
1791-
1792-       $info = pathinfo($file);
1793-       $dir = $info['dirname'];
1794-       $ext = $info['extension'];
1795-       $name = wp_basename($file, ".$ext");
1796-
1797-       if ( !is_null($dest_path) and $_dest_path = realpath($dest_path) )
1798-               $dir = $_dest_path;
1799-       $destfilename = "{$dir}/{$name}-{$suffix}.{$ext}";
1800-
1801-       if ( IMAGETYPE_GIF == $orig_type ) {
1802-               if ( !imagegif( $newimage, $destfilename ) )
1803-                       return new WP_Error('resize_path_invalid', __( 'Resize path invalid' ));
1804-       } elseif ( IMAGETYPE_PNG == $orig_type ) {
1805-               if ( !imagepng( $newimage, $destfilename ) )
1806-                       return new WP_Error('resize_path_invalid', __( 'Resize path invalid' ));
1807-       } else {
1808-               // all other formats are converted to jpg
1809-               if ( 'jpg' != $ext && 'jpeg' != $ext )
1810-                       $destfilename = "{$dir}/{$name}-{$suffix}.jpg";
1811-               if ( !imagejpeg( $newimage, $destfilename, apply_filters( 'jpeg_quality', $jpeg_quality, 'image_resize' ) ) )
1812-                       return new WP_Error('resize_path_invalid', __( 'Resize path invalid' ));
1813-       }
1814-
1815-       imagedestroy( $newimage );
1816-
1817-       // Set correct file permissions
1818-       $stat = stat( dirname( $destfilename ));
1819-       $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
1820-       @ chmod( $destfilename, $perms );
1821-
1822-       return $destfilename;
1823-}
1824-
1825-/**
1826  * Resize an image to make a thumbnail or intermediate size.
1827  *
1828  * The returned array has the file size, the image width, and image height. The
1829@@ -493,16 +379,18 @@ function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $de
1830  * @param bool $crop Optional, default is false. Whether to crop image to specified height and width or resize.
1831  * @return bool|array False, if no image was created. Metadata array on success.
1832  */
1833-function image_make_intermediate_size($file, $width, $height, $crop=false) {
1834+function image_make_intermediate_size( $file, $width, $height, $crop = false ) {
1835        if ( $width || $height ) {
1836-               $resized_file = image_resize($file, $width, $height, $crop);
1837-               if ( !is_wp_error($resized_file) && $resized_file && $info = getimagesize($resized_file) ) {
1838-                       $resized_file = apply_filters('image_make_intermediate_size', $resized_file);
1839-                       return array(
1840-                               'file' => wp_basename( $resized_file ),
1841-                               'width' => $info[0],
1842-                               'height' => $info[1],
1843-                       );
1844+               $editor = WP_Image_Editor::get_instance( $file );
1845+
1846+               if ( is_wp_error( $editor->resize( $width, $height, $crop ) ) );
1847+                       return false;
1848+
1849+               $resized_file = $editor->save();
1850+
1851+               if ( ! is_wp_error( $resized_file ) && $resized_file ) {
1852+                       unset( $resized_file['path'] );
1853+                       return $resized_file;
1854                }
1855        }
1856        return false;
1857@@ -1047,6 +935,7 @@ function gd_edit_image_support($mime_type) {
1858 
1859 /**
1860  * Create new GD image resource with transparency support
1861+ * @TODO: Deprecate if possible.
1862  *
1863  * @since 2.9.0
1864  *
1865diff --git wp-settings.php wp-settings.php
1866index 0b81e4d..470ed9a 100644
1867--- wp-settings.php
1868+++ wp-settings.php
1869@@ -143,6 +143,10 @@ require( ABSPATH . WPINC . '/nav-menu.php' );
1870 require( ABSPATH . WPINC . '/nav-menu-template.php' );
1871 require( ABSPATH . WPINC . '/admin-bar.php' );
1872 
1873+require( ABSPATH . WPINC . '/class-wp-image-editor.php' );
1874+require( ABSPATH . WPINC . '/class-wp-image-editor-gd.php' );
1875+require( ABSPATH . WPINC . '/class-wp-image-editor-imagick.php' );
1876+
1877 // Load multisite-specific files.
1878 if ( is_multisite() ) {
1879        require( ABSPATH . WPINC . '/ms-functions.php' );