Skip to content

Commit 9845632

Browse files
committed
Attachments REST API: Apply unapplied EXIF orientation before image edits
Fixes image edits (rotate, crop, flip) landing in the wrong frame for photos whose EXIF orientation tag was never applied to their pixels, most visibly iPhone JPEGs. Rotating such a photo in the media editor modal or the Image block cropper previously appeared to do nothing. `WP_REST_Attachments_Controller::edit_media_item()` edits the image from `wp_get_original_image_path()`. That original image often still carries an unapplied EXIF orientation tag. `WP_REST_Attachments_Controller::edit_media_item()` now calls `$image_editor->maybe_exif_rotate()` before modifying the image for cropping. Developed in: [https://github.com/WordPress/wordpress-develop/pull/12492](https://github.com/WordPress/wordpress-develop/pull/12492) Props ramonopoly, andrewserong, adamsilverstein. Fixes #65618. git-svn-id: https://develop.svn.wordpress.org/trunk@62820 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 921d09c commit 9845632

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,7 @@ public function edit_media_item_permissions_check( $request ) {
959959
*
960960
* @since 5.5.0
961961
* @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post.
962+
* @since 7.1.0 Applies EXIF orientation correction before image modifications.
962963
*
963964
* @param WP_REST_Request $request Full details about the request.
964965
* @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
@@ -1064,6 +1065,9 @@ public function edit_media_item( $request ) {
10641065
);
10651066
}
10661067

1068+
// Apply any unapplied EXIF orientation so edits run in the upright frame the client previewed.
1069+
$image_editor->maybe_exif_rotate();
1070+
10671071
foreach ( $modifiers as $modifier ) {
10681072
$args = $modifier['args'];
10691073
switch ( $modifier['type'] ) {

tests/phpunit/tests/rest-api/rest-attachments-controller.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3149,6 +3149,89 @@ public function test_edit_image_crop_one_axis() {
31493149
);
31503150
}
31513151

3152+
/**
3153+
* @ticket 65618
3154+
* @requires function imagejpeg
3155+
*/
3156+
public function test_edit_image_returns_error_if_no_image_editor() {
3157+
wp_set_current_user( self::$superadmin_id );
3158+
$attachment = self::factory()->attachment->create_upload_object( self::$test_file );
3159+
3160+
add_filter( 'wp_image_editors', '__return_empty_array' );
3161+
3162+
$request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" );
3163+
$request->set_body_params(
3164+
array(
3165+
'rotation' => 60,
3166+
'src' => wp_get_attachment_image_url( $attachment, 'full' ),
3167+
)
3168+
);
3169+
$response = rest_do_request( $request );
3170+
$this->assertErrorResponse( 'rest_unknown_image_file_type', $response, 500 );
3171+
}
3172+
3173+
/**
3174+
* @ticket 65618
3175+
* @requires function imagejpeg
3176+
*/
3177+
public function test_edit_image_applies_unbaked_exif_orientation_before_edits() {
3178+
wp_set_current_user( self::$superadmin_id );
3179+
$attachment = self::factory()->attachment->create_upload_object( self::$test_file );
3180+
3181+
$this->setup_mock_editor();
3182+
add_filter(
3183+
'wp_image_maybe_exif_rotate',
3184+
static function () {
3185+
return 6;
3186+
}
3187+
);
3188+
WP_Image_Editor_Mock::$edit_return['rotate'] = new WP_Error();
3189+
3190+
$request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" );
3191+
$request->set_body_params(
3192+
array(
3193+
'rotation' => 60,
3194+
'src' => wp_get_attachment_image_url( $attachment, 'full' ),
3195+
)
3196+
);
3197+
$response = rest_do_request( $request );
3198+
$this->assertErrorResponse( 'rest_image_rotation_failed', $response, 500 );
3199+
3200+
// The EXIF orientation correction (orientation 6 => rotate 270) must run before the requested edit.
3201+
$this->assertSame( array( array( 270 ), array( -60 ) ), WP_Image_Editor_Mock::$spy['rotate'] );
3202+
}
3203+
3204+
/**
3205+
* @ticket 65618
3206+
* @requires extension exif
3207+
* @requires function imagejpeg
3208+
*/
3209+
public function test_edit_image_rotate_with_unbaked_exif_orientation() {
3210+
wp_set_current_user( self::$superadmin_id );
3211+
$attachment = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg' );
3212+
3213+
$request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" );
3214+
$request->set_body_params(
3215+
array(
3216+
'rotation' => 90,
3217+
'src' => wp_get_attachment_image_url( $attachment, 'full' ),
3218+
)
3219+
);
3220+
$response = rest_do_request( $request );
3221+
$item = $response->get_data();
3222+
3223+
$this->assertSame( 201, $response->get_status() );
3224+
3225+
/*
3226+
* The original file is 1200x1800 raw pixels with an unapplied EXIF orientation of 6,
3227+
* so clients preview it upright as 1800x1200. Rotating that upright frame 90 degrees
3228+
* must produce 1200x1800. Without the orientation correction the edit rotates the raw
3229+
* pixels instead and produces 1800x1200.
3230+
*/
3231+
$this->assertSame( 1200, $item['media_details']['width'] );
3232+
$this->assertSame( 1800, $item['media_details']['height'] );
3233+
}
3234+
31523235
/**
31533236
* @ticket 44405
31543237
* @requires function imagejpeg

0 commit comments

Comments
 (0)