Skip to content

Commit

Permalink
Add Rich image editing capabilities to Gutenberg (#21024)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcus Kazmierczak <marcus@mkaz.com>
Co-authored-by: jasmussen <joen@automattic.com>
  • Loading branch information
3 people authored Jun 2, 2020
1 parent 8f892e6 commit 87fdc2c
Show file tree
Hide file tree
Showing 17 changed files with 1,445 additions and 0 deletions.
184 changes: 184 additions & 0 deletions lib/class-wp-rest-image-editor-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?php
/**
* Start: Include for phase 2
* REST API: WP_REST_Menus_Controller class
*
* @package WordPress
* @subpackage REST_API
*/

/**
* Image editor
*/
include_once __DIR__ . '/image-editor/class-image-editor.php';

/**
* Controller which provides REST API endpoints for image editing.
*
* @since 7.x ?
*
* @see WP_REST_Controller
*/
class WP_REST_Image_Editor_Controller extends WP_REST_Controller {

/**
* Constructs the controller.
*
* @since 7.x ?
* @access public
*/
public function __construct() {
$this->namespace = '__experimental';
$this->rest_base = '/richimage/(?P<mediaID>[\d]+)';
$this->editor = new Image_Editor();
}

/**
* Registers the necessary REST API routes.
*
* @since 7.x ?
* @access public
*/
public function register_routes() {
register_rest_route(
$this->namespace,
$this->rest_base . '/rotate',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'rotate_image' ),
'permission_callback' => array( $this, 'permission_callback' ),
'args' => array(
'angle' => array(
'type' => 'integer',
'required' => true,
),
),
),
)
);

register_rest_route(
$this->namespace,
$this->rest_base . '/flip',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'flip_image' ),
'permission_callback' => array( $this, 'permission_callback' ),
'args' => array(
'direction' => array(
'type' => 'enum',
'enum' => array( 'vertical', 'horizontal' ),
'required' => true,
),
),
),
)
);

register_rest_route(
$this->namespace,
$this->rest_base . '/crop',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'crop_image' ),
'permission_callback' => array( $this, 'permission_callback' ),
'args' => array(
'cropX' => array(
'type' => 'float',
'minimum' => 0,
'required' => true,
),
'cropY' => array(
'type' => 'float',
'minimum' => 0,
'required' => true,
),
'cropWidth' => array(
'type' => 'float',
'minimum' => 1,
'required' => true,
),
'cropHeight' => array(
'type' => 'float',
'minimum' => 1,
'required' => true,
),
),
),
)
);
}

/**
* Checks if the user has permissions to make the request.
*
* @since 7.x ?
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function permission_callback( $request ) {
$params = $request->get_params();

if ( ! current_user_can( 'edit_post', $params['mediaID'] ) ) {
return new WP_Error( 'rest_cannot_edit_image', __( 'Sorry, you are not allowed to edit images.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
}

return true;
}

/**
* Rotates an image.
*
* @since 7.x ?
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return array|WP_Error If successful image JSON for the modified image, otherwise a WP_Error.
*/
public function rotate_image( $request ) {
$params = $request->get_params();

$modifier = new Image_Editor_Rotate( $params['angle'] );

return $this->editor->modify_image( $params['mediaID'], $modifier );
}

/**
* Flips/mirrors an image.
*
* @since 7.x ?
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return array|WP_Error If successful image JSON for the modified image, otherwise a WP_Error.
*/
public function flip_image( $request ) {
$params = $request->get_params();

$modifier = new Image_Editor_Flip( $params['direction'] );

return $this->editor->modify_image( $params['mediaID'], $modifier );
}

/**
* Crops an image.
*
* @since 7.x ?
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return array|WP_Error If successful image JSON for the modified image, otherwise a WP_Error.
*/
public function crop_image( $request ) {
$params = $request->get_params();

$modifier = new Image_Editor_Crop( $params['cropX'], $params['cropY'], $params['cropWidth'], $params['cropHeight'] );

return $this->editor->modify_image( $params['mediaID'], $modifier );
}
}
126 changes: 126 additions & 0 deletions lib/image-editor/class-image-editor-crop.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php
/**
* Start: Include for phase 2
* Image Editor: Image_Editor_Crop class
*
* @package gutenberg
* @since 7.x ?
*/

/**
* Crop image modifier.
*/
class Image_Editor_Crop extends Image_Editor_Modifier {
/**
* Pixels from the left for the crop.
*
* @var integer
*/
private $crop_x = 0;

/**
* Pixels from the top for the crop.
*
* @var integer
*/
private $crop_y = 0;

/**
* Width in pixels for the crop.
*
* @var integer
*/
private $width = 0;

/**
* Height in pixels for the crop.
*
* @var integer
*/
private $height = 0;

/**
* Constructor.
*
* Will populate object properties from the provided arguments.
*
* @param integer $crop_x Pixels from the left for the crop.
* @param integer $crop_y Pixels from the top for the crop.
* @param integer $width Width in pixels for the crop.
* @param integer $height Height in pixels for the crop.
*/
public function __construct( $crop_x, $crop_y, $width, $height ) {
$this->crop_x = floatval( $crop_x );
$this->crop_y = floatval( $crop_y );
$this->width = floatval( $width );
$this->height = floatval( $height );
}

/**
* Update the image metadata with the modifier.
*
* @access public
*
* @param array $meta Metadata to update.
* @return array Updated metadata.
*/
public function apply_to_meta( $meta ) {
$meta['cropX'] = $this->crop_x;
$meta['cropY'] = $this->crop_y;
$meta['cropWidth'] = $this->width;
$meta['cropHeight'] = $this->height;

return $meta;
}

/**
* Apply the modifier to the image
*
* @access public
*
* @param WP_Image_Editor $image Image editor.
* @return bool|WP_Error True on success, WP_Error object or false on failure.
*/
public function apply_to_image( $image ) {
$size = $image->get_size();

$crop_x = round( ( $size['width'] * $this->crop_x ) / 100.0 );
$crop_y = round( ( $size['height'] * $this->crop_y ) / 100.0 );
$width = round( ( $size['width'] * $this->width ) / 100.0 );
$height = round( ( $size['height'] * $this->height ) / 100.0 );

return $image->crop( $crop_x, $crop_y, $width, $height );
}

/**
* Gets the new filename based on metadata.
*
* @access public
*
* @param array $meta Image metadata.
* @return string Filename for the edited image.
*/
public static function get_filename( $meta ) {
if ( isset( $meta['cropWidth'] ) && $meta['cropWidth'] > 0 ) {
$target_file = sprintf( 'crop-%d-%d-%d-%d', round( $meta['cropX'], 2 ), round( $meta['cropY'], 2 ), round( $meta['cropWidth'], 2 ), round( $meta['cropHeight'], 2 ) );

// We need to change the original name to include the crop. This way if it's cropped again we won't clash.
$meta['original_name'] = $target_file;

return $target_file;
}

return false;
}

/**
* Gets the default metadata for the crop modifier.
*
* @access public
*
* @return array Default metadata.
*/
public static function get_default_meta() {
return array();
}
}
Loading

0 comments on commit 87fdc2c

Please sign in to comment.