WordPress.org

Make WordPress Core

Ticket #14459: 14459.5.diff

File 14459.5.diff, 9.3 KB (added by azaozz, 4 weeks ago)
  • src/wp-admin/includes/image.php

     
    159159}
    160160
    161161/**
     162 * Updates the attached file and image meta data when the original image was edited.
     163 *
     164 * @since 5.3.0
     165 * @access private
     166 *
     167 * @param array  $saved_data    The data retirned from WP_Image_Editor after successfully saving an image.
     168 * @param string $original_file Path to the original file.
     169 * @param array  $image_meta    The image meta data.
     170 * @param int    $attachment_id The attachment post ID.
     171 * @return array The updated image meta data.
     172 */
     173function _wp_image_meta_replace_original( $saved_data, $original_file, $image_meta, $attachment_id ) {
     174        $new_file = $saved_data['path'];
     175
     176        // Update the attached file meta.
     177        update_attached_file( $attachment_id, $new_file );
     178
     179        // Width and height of the new image.
     180        $image_meta['width']  = $saved_data['width'];
     181        $image_meta['height'] = $saved_data['height'];
     182
     183        // Make the file path relative to the upload dir.
     184        $image_meta['file'] = _wp_relative_upload_path( $new_file );
     185
     186        // Store the original image file name in image_meta.
     187        $image_meta['original_image'] = wp_basename( $original_file );
     188
     189        return $image_meta;
     190}
     191
     192/**
    162193 * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
    163194 *
    164195 * Intended for use after an image is uploaded. Saves/updates the image metadata after each
     
    222253
    223254                // Resize the image
    224255                $resized = $editor->resize( $threshold, $threshold );
     256                $rotated = null;
    225257
     258                // Rotate JPEGs according to EXIF Orientation.
     259                if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
     260                        $resized = $editor->maybe_exif_rotate();
     261                        $rotated = $resized;
     262                }
     263
    226264                if ( ! is_wp_error( $resized ) ) {
    227                         // TODO: EXIF rotate here.
    228                         // By default the editor will append `{width}x{height}` to the file name of the resized image.
    229                         // Better to append the threshold size instead so the image file name would be like "my-image-2560.jpg"
    230                         // and not look like a "regular" sub-size.
     265                        // Append the threshold size to the image file name. It will look like "my-image-2560.jpg".
    231266                        // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
    232267                        $saved = $editor->save( $editor->generate_filename( $threshold ) );
    233268
    234269                        if ( ! is_wp_error( $saved ) ) {
    235                                 $new_file = $saved['path'];
     270                                $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
    236271
    237                                 // Update the attached file meta.
    238                                 update_attached_file( $attachment_id, $new_file );
     272                                // If the image was rotated update the stored EXIF data.
     273                                if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
     274                                        $image_meta['image_meta']['orientation'] = 1;
     275                                }
     276                        } else {
     277                                // TODO: handle errors.
     278                        }
     279                } else {
     280                        // TODO: handle errors.
     281                }
     282        } elseif ( ! empty( $exif_meta['orientation'] ) && (int) $exif_meta['orientation'] !== 1 ) {
     283                // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
    239284
    240                                 // Width and height of the new image.
    241                                 $image_meta['width']  = $saved['width'];
    242                                 $image_meta['height'] = $saved['height'];
     285                $editor = wp_get_image_editor( $file );
    243286
    244                                 // Make the file path relative to the upload dir.
    245                                 $image_meta['file'] = _wp_relative_upload_path( $new_file );
     287                if ( is_wp_error( $editor ) ) {
     288                        // This image cannot be edited.
     289                        return $image_meta;
     290                }
    246291
    247                                 // Store the original image file name in image_meta.
    248                                 $image_meta['original_image'] = wp_basename( $file );
     292                // Rotate the image
     293                $rotated = $editor->maybe_exif_rotate();
     294
     295                if ( true === $rotated ) {
     296                        // Append `-rotated` to the image file name.
     297                        $saved = $editor->save( $editor->generate_filename( 'rotated' ) );
     298
     299                        if ( ! is_wp_error( $saved ) ) {
     300                                $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     301
     302                                // Update the stored EXIF data.
     303                                if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
     304                                        $image_meta['image_meta']['orientation'] = 1;
     305                                }
     306                        } else {
     307                                // TODO: handle errors.
    249308                        }
    250309                }
    251310        }
     
    327386                return $image_meta;
    328387        }
    329388
     389        // If stored EXIF data exists, rotate the source image before creating sub-sizes.
     390        if ( ! empty( $image_meta['image_meta'] ) ) {
     391                $rotated = $editor->maybe_exif_rotate();
     392
     393                if ( is_wp_error( $rotated ) ) {
     394                        // TODO: handle errors.
     395                }
     396        }
     397
    330398        if ( method_exists( $editor, 'make_subsize' ) ) {
    331399                foreach ( $new_sizes as $new_size_name => $new_size_data ) {
    332400                        $new_size_meta = $editor->make_subsize( $new_size_data );
  • src/wp-includes/class-wp-image-editor-imagick.php

     
    566566                try {
    567567                        $this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
    568568
    569                         // Normalise Exif orientation data so that display is consistent across devices.
     569                        // Normalise EXIF orientation data so that display is consistent across devices.
    570570                        if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
    571571                                $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
    572572                        }
     
    602602                        if ( $vert ) {
    603603                                $this->image->flopImage();
    604604                        }
     605
     606                        // Normalise EXIF orientation data so that display is consistent across devices.
     607                        if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
     608                                $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
     609                        }
    605610                } catch ( Exception $e ) {
    606611                        return new WP_Error( 'image_flip_error', $e->getMessage() );
    607612                }
     613
    608614                return true;
    609615        }
    610616
    611617        /**
     618         * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
     619         *
     620         * As Imagick copies the EXIF data to the flipped/rotated image, proceed only
     621         * if the EXIF Orientation tag can be reset afterwards.
     622         *
     623         * @since 5.3.0
     624         *
     625         * @return bool|WP_Error True if the image was rotated. False if no EXIF data or if the image doesn't need rotation.
     626         *                       WP_Error if error while rotating.
     627         */
     628        public function maybe_exif_rotate() {
     629                if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
     630                        return parent::maybe_exif_rotate();
     631                } else {
     632                        return new WP_Error( 'write_exif_error', __( 'The image cannot be rotated because the embedded meta data cannot be updated.' ) );
     633                }
     634        }
     635
     636        /**
    612637         * Saves current image to file.
    613638         *
    614639         * @since 3.5.0
  • src/wp-includes/class-wp-image-editor.php

     
    385385        }
    386386
    387387        /**
     388         * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
     389         *
     390         * @since 5.3.0
     391         *
     392         * @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).
     393         *                       WP_Error if error while rotating.
     394         */
     395        public function maybe_exif_rotate() {
     396                $orientation = null;
     397
     398                if ( is_callable( 'exif_read_data' ) && 'image/jpeg' === $this->mime_type ) {
     399                        $exif_data = @exif_read_data( $this->file );
     400
     401                        if ( ! empty( $exif_data['Orientation'] ) ) {
     402                                $orientation = (int) $exif_data['Orientation'];
     403                        }
     404                }
     405
     406                /**
     407                 * Allows themes and plugins to prevemnt rotating the image, or to correct the `$orientation` value before rotating.
     408                 *
     409                 * @since 5.3.0
     410                 *
     411                 * @param int    $orientation EXIF Orientation value as retrieved from the image file.
     412                 * @param string $file        Path to the image file.
     413                 */
     414                $orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $this->file );
     415
     416                if ( ! $orientation || $orientation === 1 ) {
     417                        return false;
     418                }
     419
     420                switch ( $orientation ) {
     421                        case 2:
     422                                // Flip horizontally.
     423                                $result = $this->flip( true, false );
     424                                break;
     425                        case 3:
     426                                // Rotate 180 degrees or flip horizontally and vertically.
     427                                // Flipping seems faster/uses less resources.
     428                                $result = $this->flip( true, true );
     429                                break;
     430                        case 4:
     431                                // Flip vertically.
     432                                $result = $this->flip( false, true );
     433                                break;
     434                        case 5:
     435                                // Rotate 90 degrees counter-clockwise and flip vertically.
     436                                $result = $this->rotate( 90 );
     437
     438                                if ( ! is_wp_error( $result ) ) {
     439                                        $result = $this->flip( false, true );
     440                                }
     441
     442                                break;
     443                        case 6:
     444                                // Rotate 90 degrees clockwise (270 counter-clockwise).
     445                                $result = $this->rotate( 270 );
     446                                break;
     447                        case 7:
     448                                // Rotate 90 degrees counter-clockwise and flip horizontally.
     449                                $result = $this->rotate( 90 );
     450
     451                                if ( ! is_wp_error( $result ) ) {
     452                                        $result = $this->flip( true, false );
     453                                }
     454
     455                                break;
     456                        case 8:
     457                                // Rotate 90 degrees counter-clockwise.
     458                                $result = $this->rotate( 90 );
     459                                break;
     460                }
     461
     462                return $result;
     463        }
     464
     465        /**
    388466         * Either calls editor's save function or handles file as a stream.
    389467         *
    390468         * @since 3.5.0