Skip to content

Commit

Permalink
Image editing: batch editing in cropper component (#23284)
Browse files Browse the repository at this point in the history
* Image editing: batch editing in cropper component

* For rotation, set new image url because rotation is limited in cropper

* Don' add editor for images not hosted on the server

* Fix loading indicator

* Fix image edit without rotate

* Reorg buttons

* Recalculate height after aspect ratio change

* Remove unused endpoints

* Fix php lint errors
  • Loading branch information
ellatrix committed Jun 21, 2020
1 parent 5a341a8 commit 5886225
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 1,031 deletions.
195 changes: 99 additions & 96 deletions lib/class-wp-rest-image-editor-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
* @subpackage REST_API
*/

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

/**
* Controller which provides REST API endpoints for image editing.
*
Expand All @@ -30,7 +25,6 @@ class WP_REST_Image_Editor_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = '__experimental';
$this->rest_base = '/richimage/(?P<media_id>[\d]+)';
$this->editor = new Image_Editor();
}

/**
Expand All @@ -42,75 +36,35 @@ public function __construct() {
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(
'description' => __( 'Rotation angle', 'gutenberg' ),
'type' => 'integer',
'required' => true,
),
),
),
)
);

register_rest_route(
$this->namespace,
$this->rest_base . '/flip',
$this->rest_base . '/apply',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'flip_image' ),
'callback' => array( $this, 'apply_edits' ),
'permission_callback' => array( $this, 'permission_callback' ),
'args' => array(
'direction' => array(
'description' => __( 'Flip direction', 'gutenberg' ),
'type' => 'string',
'enum' => array( 'vertical', 'horizontal' ),
'required' => true,
'x' => array(
'type' => 'float',
'minimum' => 0,
'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(
'crop_x' => array(
'description' => __( 'Crop offset percentage from left', 'gutenberg' ),
'type' => 'number',
'minimum' => 0,
'required' => true,
'y' => array(
'type' => 'float',
'minimum' => 0,
'required' => true,
),
'crop_y' => array(
'description' => __( 'Crop offset percentage from top', 'gutenberg' ),
'type' => 'number',
'minimum' => 0,
'required' => true,
'width' => array(
'type' => 'float',
'minimum' => 1,
'required' => true,
),
'crop_width' => array(
'description' => __( 'Crop width percentage', 'gutenberg' ),
'type' => 'number',
'minimum' => 1,
'required' => true,
'height' => array(
'type' => 'float',
'minimum' => 1,
'required' => true,
),
'crop_height' => array(
'description' => __( 'Crop height percentage', 'gutenberg' ),
'type' => 'number',
'minimum' => 1,
'required' => true,
'rotation' => array(
'type' => 'integer',
),
),
),
Expand All @@ -136,47 +90,96 @@ public function permission_callback( $request ) {
}

/**
* Rotates an image.
* Applies all edits in one go.
*
* @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 ) {
$modifier = new Image_Editor_Rotate( $request['angle'] );
public function apply_edits( $request ) {
require_once ABSPATH . 'wp-admin/includes/image.php';

return $this->editor->modify_image( $request['media_id'], $modifier );
}
$params = $request->get_params();

/**
* 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 ) {
$modifier = new Image_Editor_Flip( $request['direction'] );
$media_id = $params['media_id'];

return $this->editor->modify_image( $request['media_id'], $modifier );
}
// Get image information.
$attachment_info = wp_get_attachment_metadata( $media_id );
$media_url = wp_get_attachment_image_url( $media_id, 'original' );

/**
* 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 ) {
$modifier = new Image_Editor_Crop( $request['crop_x'], $request['crop_y'], $request['crop_width'], $request['crop_height'] );
if ( ! $attachment_info || ! $media_url ) {
return new WP_Error( 'unknown', 'Unable to get meta information for file' );
}

$meta = array( 'original_name' => basename( $media_url ) );

if ( isset( $attachment_info['richimage'] ) ) {
$meta = array_merge( $meta, $attachment_info['richimage'] );
}

// Try and load the image itself.
$image_path = get_attached_file( $media_id );
if ( empty( $image_path ) ) {
return new WP_Error( 'fileunknown', 'Unable to find original media file' );
}

$image_editor = wp_get_image_editor( $image_path );
if ( ! $image_editor->load() ) {
return new WP_Error( 'fileload', 'Unable to load original media file' );
}

return $this->editor->modify_image( $request['media_id'], $modifier );
$size = $image_editor->get_size();

// Finally apply the modifications.
$crop_x = round( ( $size['width'] * floatval( $params['x'] ) ) / 100.0 );
$crop_y = round( ( $size['height'] * floatval( $params['y'] ) ) / 100.0 );
$width = round( ( $size['width'] * floatval( $params['width'] ) ) / 100.0 );
$height = round( ( $size['height'] * floatval( $params['height'] ) ) / 100.0 );
$image_editor->crop( $crop_x, $crop_y, $width, $height );

if ( isset( $params['rotation'] ) ) {
$image_editor->rotate( 0 - $params['rotation'] );
}

// TODO: Generate filename based on edits.
$target_file = 'edited-' . $meta['original_name'];

$filename = rtrim( dirname( $image_path ), '/' ) . '/' . $target_file;

// Save to disk.
$saved = $image_editor->save( $filename );

if ( is_wp_error( $saved ) ) {
return $saved;
}

// Update attachment details.
$attachment_post = array(
'guid' => $saved['path'],
'post_mime_type' => $saved['mime-type'],
'post_title' => pathinfo( $target_file, PATHINFO_FILENAME ),
'post_content' => '',
'post_status' => 'inherit',
);

// Add this as an attachment.
$attachment_id = wp_insert_attachment( $attachment_post, $saved['path'], 0 );
if ( 0 === $attachment_id ) {
return new WP_Error( 'attachment', 'Unable to add image as attachment' );
}

// Generate thumbnails.
$metadata = wp_generate_attachment_metadata( $attachment_id, $saved['path'] );

$metadata['richimage'] = $meta;

wp_update_attachment_metadata( $attachment_id, $metadata );

return array(
'media_id' => $attachment_id,
'url' => wp_get_attachment_image_url( $attachment_id, 'original' ),
);
}
}
126 changes: 0 additions & 126 deletions lib/image-editor/class-image-editor-crop.php

This file was deleted.

Loading

0 comments on commit 5886225

Please sign in to comment.