Index: src/wp-admin/includes/image.php
===================================================================
--- src/wp-admin/includes/image.php	(revision 39616)
+++ src/wp-admin/includes/image.php	(working copy)
@@ -234,7 +234,7 @@
 
 		// Only load PDFs in an image editor if we're processing sizes.
 		if ( ! empty( $sizes ) ) {
-			$editor = wp_get_image_editor( $file );
+			$editor = wp_get_image_editor( $file, array( 'type' => 'pdf' ) ); // Signal doing PDF.
 
 			if ( ! is_wp_error( $editor ) ) { // No support for this type of file
 				$uploaded = $editor->save( $file, 'image/jpeg' );
Index: src/wp-includes/class-wp-image-editor-gs.php
===================================================================
--- src/wp-includes/class-wp-image-editor-gs.php	(nonexistent)
+++ src/wp-includes/class-wp-image-editor-gs.php	(working copy)
@@ -0,0 +1,311 @@
+<?php
+/**
+ * WordPress GhostScript Image Editor
+ *
+ * @package WordPress
+ * @subpackage Image_Editor
+ */
+
+/**
+ * WordPress Image Editor Class for producing JPEG from PDF using GhostScript.
+ *
+ * @since 4.x
+ * @package WordPress
+ * @subpackage Image_Editor
+ * @uses WP_Image_Editor Extends class
+ */
+class WP_Image_Editor_GS extends WP_Image_Editor {
+
+	/**
+	 * Resolution of output JPEG.
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	protected $resolution = '128x128';
+
+	/**
+	 * Checks to see if current environment supports GhostScript.
+	 *
+	 * @since 4.x
+	 *
+	 * @static
+	 * @access public
+	 *
+	 * @staticvar array $have_gs
+	 *
+	 * @param array $args
+	 * @return bool
+	 */
+	public static function test( $args = array() ) {
+		static $have_gs = null;
+
+		if ( null === $have_gs ) {
+			$cmd = self::gs_cmd( '-dBATCH -dNOPAUSE -dNOPROMPT -dSAFER -v' );
+			exec( $cmd, $output, $return_var );
+
+			if ( 0 !== $return_var ) {
+				$have_gs = false;
+			} elseif ( empty( $output[0] ) || false === stripos( $output[0], 'ghostscript' ) ) {
+				$have_gs = false;
+			} else {
+				$have_gs = true;
+			}
+		}
+
+		if ( ! $have_gs ) {
+			return false;
+		}
+
+		if ( isset( $args['methods'] ) ) {
+			$unsupported_methods = array( 'resize', 'multi_resize', 'crop', 'rotate', 'flip', 'stream' );
+			if ( array_intersect( $unsupported_methods, $args['methods'] ) ) {
+				return false;
+			}
+		}
+
+		// Loading from URL not currently supported.
+		if ( isset( $args['path'] ) && preg_match( '|^https?://|', $args['path'] ) ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Checks to see if editor supports the mime-type specified.
+	 *
+	 * @since 4.x
+	 *
+	 * @static
+	 * @access public
+	 *
+	 * @param string $mime_type
+	 * @return bool
+	 */
+	public static function supports_mime_type( $mime_type ) {
+		return 'PDF' === strtoupper( self::get_extension( $mime_type ) );
+	}
+
+	/**
+	 * Checks existence and sets mime type and calls `set_quality`.
+	 *
+	 * @since 4.x
+	 * @access protected
+	 *
+	 * @return true|WP_Error True if loaded; WP_Error on failure.
+	 */
+	public function load() {
+		if ( ! is_file( $this->file ) ) {
+			return new WP_Error( 'error_loading_image', __( 'File doesn&#8217;t exist?' ), $this->file );
+		}
+
+		list( $filename, $extension, $mime_type ) = $this->get_output_format( $this->file );
+		$this->mime_type = $mime_type;
+
+		return $this->set_quality();
+	}
+
+	/**
+	 * Creates JPEG from PDF.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param string $destfilename
+	 * @param string $mime_type
+	 * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
+	 */
+	public function save( $destfilename = null, $mime_type = null ) {
+		list( $filename, $extension, $mime_type ) = $this->get_output_format( $destfilename, $mime_type );
+
+		if ( 'image/jpeg' !== $mime_type ) {
+			return new WP_Error( 'image_save_error', __( 'Unsupported mime type.' ) );
+		}
+
+		if ( ! $filename ) {
+			$filename = $this->generate_filename( null, null, $extension );
+		}
+		$pdf_filename = $this->file;
+
+		// TODO: Probably some escaping and Windows/IIS issues.
+		$cmd = self::gs_cmd( $this->get_gs_args( $filename  ) . ' ' .  escapeshellarg( $pdf_filename ) );
+		exec( $cmd, $output, $return_val );
+
+		if ( 0 !== $return_val ) {
+			return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed.' ) );
+		}
+
+		$size = @ getimagesize( $filename );
+		if ( ! $size ) {
+			return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed.' ) );
+		}
+
+		// Transmogrify into the JPEG file.
+		$this->file = $filename;
+		$this->mime_type = $mime_type;
+		$this->update_size( $size[0], $size[1] );
+
+		// Set correct file permissions
+		$stat = stat( dirname( $filename ) );
+		$perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
+		@ chmod( $filename, $perms );
+
+		/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
+		return array(
+			'path'      => $filename,
+			'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
+			'width'     => $this->size['width'],
+			'height'    => $this->size['height'],
+			'mime-type' => $mime_type,
+		);
+	}
+
+	/**
+	 * Process shell command to lessen noise.
+	 *
+	 * @since 4.x
+	 * @access protected
+	 *
+	 * @param string $cmd Command.
+	 * @return string
+	 */
+	protected static function gs_cmd( $args ) {
+		global $is_IIS;
+		if ( $is_IIS ) {
+			$args .= ' -sstdout=NUL';
+		} else {
+			$args .= ' -sstdout=/dev/null';
+		}
+		// Redirection gets escaped in safe mode.
+		if ( ! ini_get( 'safe_mode' ) ) {
+			$args .= ' 2>1';
+		}
+		return 'gs ' . $args;
+	}
+
+	/**
+	 * Get the arguments for main GhostScript invocation.
+	 *
+	 * @since 4.x
+	 * @access protected
+	 *
+	 * @param string $filename File name of output JPEG.
+	 * @return string
+	 */
+	protected function get_gs_args( $filename ) {
+		$ret = '-dBATCH -dFirstPage=1 -dLastPage=1 -dNOPAUSE -dNOPROMPT -dQUIET -q -sDEVICE=jpeg';
+
+		// TODO: Probably some escaping and Windows/IIS issues.
+		if ( $quality = $this->get_quality() ) {
+			$ret .= ' ' . escapeshellarg( sprintf( '-dJPEGQ=%d', $quality ) );
+		}
+		if ( $this->resolution ) {
+			$ret .= ' ' . escapeshellarg( '-r' . $this->resolution );
+		}
+		if ( $filename ) {
+			$ret .= ' ' . escapeshellarg( '-sOutputFile=' . $filename );
+		}
+
+		return $ret;
+	}
+
+	/**
+	 * Resizes current image. Unsupported.
+	 *
+	 * At minimum, either a height or width must be provided.
+	 * If one of the two is set to null, the resize will
+	 * maintain aspect ratio according to the provided dimension.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param  int|null $max_w Image width.
+	 * @param  int|null $max_h Image height.
+	 * @param  bool     $crop
+	 * @return WP_Error
+	 */
+	public function resize( $max_w, $max_h, $crop = false ) {
+		return new WP_Error( 'image_resize_error', __( 'Unsupported operation.' ) );
+	}
+
+	/**
+	 * Resize multiple images from a single source. Unsupported.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param array $sizes {
+	 *     An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
+	 *
+	 *     @type array $size {
+	 *         @type int  $width  Image width.
+	 *         @type int  $height Image height.
+	 *         @type bool $crop   Optional. Whether to crop the image. Default false.
+	 *     }
+	 * }
+	 * @return WP_Error
+	 */
+	public function multi_resize( $sizes ) {
+		return new WP_Error( 'image_multi_resize_error', __( 'Unsupported operation.' ) );
+	}
+
+	/**
+	 * Crops Image. Unsupported.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param int $src_x The start x position to crop from.
+	 * @param int $src_y The start y position to crop from.
+	 * @param int $src_w The width to crop.
+	 * @param int $src_h The height to crop.
+	 * @param int $dst_w Optional. The destination width.
+	 * @param int $dst_h Optional. The destination height.
+	 * @param bool $src_abs Optional. If the source crop points are absolute.
+	 * @return WP_Error
+	 */
+	public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
+		return new WP_Error( 'image_crop_error', __( 'Unsupported operation.' ) );
+	}
+
+	/**
+	 * Rotates current image counter-clockwise by $angle. Unsupported.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param float $angle
+	 * @return WP_Error
+	 */
+	public function rotate( $angle ) {
+		return new WP_Error( 'image_rotate_error', __( 'Unsupported operation.' ) );
+	}
+
+	/**
+	 * Flips current image. Unsupported.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param bool $horz Flip along Horizontal Axis
+	 * @param bool $vert Flip along Vertical Axis
+	 * @return WP_Error
+	 */
+	public function flip( $horz, $vert ) {
+		return new WP_Error( 'image_flip_error', __( 'Unsupported operation.' ) );
+	}
+
+	/**
+	 * Streams current image to browser. Unsupported.
+	 *
+	 * @since 4.x
+	 * @access public
+	 *
+	 * @param string $mime_type
+	 * @return WP_Error
+	 */
+	public function stream( $mime_type = null ) {
+		return new WP_Error( 'image_stream_error', __( 'Unsupported operation.' ) );
+	}
+}
Index: src/wp-includes/media.php
===================================================================
--- src/wp-includes/media.php	(revision 39616)
+++ src/wp-includes/media.php	(working copy)
@@ -2928,6 +2928,15 @@
 	require_once ABSPATH . WPINC . '/class-wp-image-editor.php';
 	require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php';
 	require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php';
+
+	$image_editors = array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ); // Defaults.
+
+	if ( isset( $args['type'] ) && 'pdf' === $args['type'] ) {
+		// For PDFs, jump in before Imagick.
+		require_once ABSPATH . WPINC . '/class-wp-image-editor-gs.php';
+		array_unshift( $image_editors, 'WP_Image_Editor_GS' );
+	}
+
 	/**
 	 * Filters the list of image editing library classes.
 	 *
@@ -2936,7 +2945,7 @@
 	 * @param array $image_editors List of available image editors. Defaults are
 	 *                             'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'.
 	 */
-	$implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );
+	$implementations = apply_filters( 'wp_image_editors', $image_editors );
 
 	foreach ( $implementations as $implementation ) {
 		if ( ! call_user_func( array( $implementation, 'test' ), $args ) )
