Media/Upload: rotate images on upload according to EXIF Orientation.
Props msaggiorato, wpdavis, markoheijnen, dhuyvetter, msaggiorato, n7studios, triplejumper12, pbiron, mikeschroder, joemcgill, azaozz. Fixes #14459. Built from https://develop.svn.wordpress.org/trunk@46202 git-svn-id: http://core.svn.wordpress.org/trunk@46014 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
86f7ac6669
commit
29ea4c6a4c
|
@ -158,6 +158,37 @@ function wp_update_image_subsizes( $attachment_id ) {
|
||||||
return _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
|
return _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the attached file and image meta data when the original image was edited.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @param array $saved_data The data retirned from WP_Image_Editor after successfully saving an image.
|
||||||
|
* @param string $original_file Path to the original file.
|
||||||
|
* @param array $image_meta The image meta data.
|
||||||
|
* @param int $attachment_id The attachment post ID.
|
||||||
|
* @return array The updated image meta data.
|
||||||
|
*/
|
||||||
|
function _wp_image_meta_replace_original( $saved_data, $original_file, $image_meta, $attachment_id ) {
|
||||||
|
$new_file = $saved_data['path'];
|
||||||
|
|
||||||
|
// Update the attached file meta.
|
||||||
|
update_attached_file( $attachment_id, $new_file );
|
||||||
|
|
||||||
|
// Width and height of the new image.
|
||||||
|
$image_meta['width'] = $saved_data['width'];
|
||||||
|
$image_meta['height'] = $saved_data['height'];
|
||||||
|
|
||||||
|
// Make the file path relative to the upload dir.
|
||||||
|
$image_meta['file'] = _wp_relative_upload_path( $new_file );
|
||||||
|
|
||||||
|
// Store the original image file name in image_meta.
|
||||||
|
$image_meta['original_image'] = wp_basename( $original_file );
|
||||||
|
|
||||||
|
return $image_meta;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
|
* Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
|
||||||
*
|
*
|
||||||
|
@ -222,30 +253,58 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
|
||||||
|
|
||||||
// Resize the image
|
// Resize the image
|
||||||
$resized = $editor->resize( $threshold, $threshold );
|
$resized = $editor->resize( $threshold, $threshold );
|
||||||
|
$rotated = null;
|
||||||
|
|
||||||
|
// If there is EXIF data, rotate according to EXIF Orientation.
|
||||||
|
if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
|
||||||
|
$resized = $editor->maybe_exif_rotate();
|
||||||
|
$rotated = $resized;
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! is_wp_error( $resized ) ) {
|
if ( ! is_wp_error( $resized ) ) {
|
||||||
// TODO: EXIF rotate here.
|
// Append the threshold size to the image file name. It will look like "my-image-2560.jpg".
|
||||||
// By default the editor will append `{width}x{height}` to the file name of the resized image.
|
|
||||||
// Better to append the threshold size instead so the image file name would be like "my-image-2560.jpg"
|
|
||||||
// and not look like a "regular" sub-size.
|
|
||||||
// This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
|
// This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
|
||||||
$saved = $editor->save( $editor->generate_filename( $threshold ) );
|
$saved = $editor->save( $editor->generate_filename( $threshold ) );
|
||||||
|
|
||||||
if ( ! is_wp_error( $saved ) ) {
|
if ( ! is_wp_error( $saved ) ) {
|
||||||
$new_file = $saved['path'];
|
$image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
|
||||||
|
|
||||||
// Update the attached file meta.
|
// If the image was rotated update the stored EXIF data.
|
||||||
update_attached_file( $attachment_id, $new_file );
|
if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
|
||||||
|
$image_meta['image_meta']['orientation'] = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: handle errors.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: handle errors.
|
||||||
|
}
|
||||||
|
} elseif ( ! empty( $exif_meta['orientation'] ) && (int) $exif_meta['orientation'] !== 1 ) {
|
||||||
|
// Rotate the whole original image if there is EXIF data and "orientation" is not 1.
|
||||||
|
|
||||||
// Width and height of the new image.
|
$editor = wp_get_image_editor( $file );
|
||||||
$image_meta['width'] = $saved['width'];
|
|
||||||
$image_meta['height'] = $saved['height'];
|
|
||||||
|
|
||||||
// Make the file path relative to the upload dir.
|
if ( is_wp_error( $editor ) ) {
|
||||||
$image_meta['file'] = _wp_relative_upload_path( $new_file );
|
// This image cannot be edited.
|
||||||
|
return $image_meta;
|
||||||
|
}
|
||||||
|
|
||||||
// Store the original image file name in image_meta.
|
// Rotate the image
|
||||||
$image_meta['original_image'] = wp_basename( $file );
|
$rotated = $editor->maybe_exif_rotate();
|
||||||
|
|
||||||
|
if ( true === $rotated ) {
|
||||||
|
// Append `-rotated` to the image file name.
|
||||||
|
$saved = $editor->save( $editor->generate_filename( 'rotated' ) );
|
||||||
|
|
||||||
|
if ( ! is_wp_error( $saved ) ) {
|
||||||
|
$image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
|
||||||
|
|
||||||
|
// Update the stored EXIF data.
|
||||||
|
if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
|
||||||
|
$image_meta['image_meta']['orientation'] = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: handle errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,6 +386,15 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
|
||||||
return $image_meta;
|
return $image_meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If stored EXIF data exists, rotate the source image before creating sub-sizes.
|
||||||
|
if ( ! empty( $image_meta['image_meta'] ) ) {
|
||||||
|
$rotated = $editor->maybe_exif_rotate();
|
||||||
|
|
||||||
|
if ( is_wp_error( $rotated ) ) {
|
||||||
|
// TODO: handle errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( method_exists( $editor, 'make_subsize' ) ) {
|
if ( method_exists( $editor, 'make_subsize' ) ) {
|
||||||
foreach ( $new_sizes as $new_size_name => $new_size_data ) {
|
foreach ( $new_sizes as $new_size_name => $new_size_data ) {
|
||||||
$new_size_meta = $editor->make_subsize( $new_size_data );
|
$new_size_meta = $editor->make_subsize( $new_size_data );
|
||||||
|
|
|
@ -566,7 +566,7 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
|
||||||
try {
|
try {
|
||||||
$this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
|
$this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
|
||||||
|
|
||||||
// Normalise Exif orientation data so that display is consistent across devices.
|
// Normalise EXIF orientation data so that display is consistent across devices.
|
||||||
if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
|
if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
|
||||||
$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
|
$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
|
||||||
}
|
}
|
||||||
|
@ -602,12 +602,37 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
|
||||||
if ( $vert ) {
|
if ( $vert ) {
|
||||||
$this->image->flopImage();
|
$this->image->flopImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalise EXIF orientation data so that display is consistent across devices.
|
||||||
|
if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
|
||||||
|
$this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
|
||||||
|
}
|
||||||
} catch ( Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
return new WP_Error( 'image_flip_error', $e->getMessage() );
|
return new WP_Error( 'image_flip_error', $e->getMessage() );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
|
||||||
|
*
|
||||||
|
* As ImageMagick copies the EXIF data to the flipped/rotated image, proceed only
|
||||||
|
* if EXIF Orientation can be reset afterwards.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*
|
||||||
|
* @return bool|WP_Error True if the image was rotated. False if no EXIF data or if the image doesn't need rotation.
|
||||||
|
* WP_Error if error while rotating.
|
||||||
|
*/
|
||||||
|
public function maybe_exif_rotate() {
|
||||||
|
if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
|
||||||
|
return parent::maybe_exif_rotate();
|
||||||
|
} else {
|
||||||
|
return new WP_Error( 'write_exif_error', __( 'The image cannot be rotated because the embedded meta data cannot be updated.' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves current image to file.
|
* Saves current image to file.
|
||||||
*
|
*
|
||||||
|
|
|
@ -384,6 +384,84 @@ abstract class WP_Image_Editor {
|
||||||
return "{$this->size['width']}x{$this->size['height']}";
|
return "{$this->size['width']}x{$this->size['height']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*
|
||||||
|
* @return bool|WP_Error True if the image was rotated. False if not rotated (no EXIF data or the image doesn't need to be rotated).
|
||||||
|
* WP_Error if error while rotating.
|
||||||
|
*/
|
||||||
|
public function maybe_exif_rotate() {
|
||||||
|
$orientation = null;
|
||||||
|
|
||||||
|
if ( is_callable( 'exif_read_data' ) && 'image/jpeg' === $this->mime_type ) {
|
||||||
|
$exif_data = @exif_read_data( $this->file );
|
||||||
|
|
||||||
|
if ( ! empty( $exif_data['Orientation'] ) ) {
|
||||||
|
$orientation = (int) $exif_data['Orientation'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the `$orientation` value to correct it before rotating or to prevemnt rotating the image.
|
||||||
|
*
|
||||||
|
* @since 5.3.0
|
||||||
|
*
|
||||||
|
* @param int $orientation EXIF Orientation value as retrieved from the image file.
|
||||||
|
* @param string $file Path to the image file.
|
||||||
|
*/
|
||||||
|
$orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $this->file );
|
||||||
|
|
||||||
|
if ( ! $orientation || $orientation === 1 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( $orientation ) {
|
||||||
|
case 2:
|
||||||
|
// Flip horizontally.
|
||||||
|
$result = $this->flip( true, false );
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Rotate 180 degrees or flip horizontally and vertically.
|
||||||
|
// Flipping seems faster/uses less resources.
|
||||||
|
$result = $this->flip( true, true );
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// Flip vertically.
|
||||||
|
$result = $this->flip( false, true );
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
// Rotate 90 degrees counter-clockwise and flip vertically.
|
||||||
|
$result = $this->rotate( 90 );
|
||||||
|
|
||||||
|
if ( ! is_wp_error( $result ) ) {
|
||||||
|
$result = $this->flip( false, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
// Rotate 90 degrees clockwise (270 counter-clockwise).
|
||||||
|
$result = $this->rotate( 270 );
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
// Rotate 90 degrees counter-clockwise and flip horizontally.
|
||||||
|
$result = $this->rotate( 90 );
|
||||||
|
|
||||||
|
if ( ! is_wp_error( $result ) ) {
|
||||||
|
$result = $this->flip( true, false );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
// Rotate 90 degrees counter-clockwise.
|
||||||
|
$result = $this->rotate( 90 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Either calls editor's save function or handles file as a stream.
|
* Either calls editor's save function or handles file as a stream.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
*
|
*
|
||||||
* @global string $wp_version
|
* @global string $wp_version
|
||||||
*/
|
*/
|
||||||
$wp_version = '5.3-alpha-46201';
|
$wp_version = '5.3-alpha-46202';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||||
|
|
Loading…
Reference in New Issue