run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄class-wp-image-editor-imagick.php
1<?php
2/**
3 * WordPress Imagick Image Editor
4 *
5 * @package WordPress
6 * @subpackage Image_Editor
7 */
8
9/**
10 * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
11 *
12 * @since 3.5.0
13 *
14 * @see WP_Image_Editor
15 */
16class WP_Image_Editor_Imagick extends WP_Image_Editor {
17 /**
18 * Imagick object.
19 *
20 * @var Imagick
21 */
22 protected $image;
23
24 public function __destruct() {
25 if ( $this->image instanceof Imagick ) {
26 // We don't need the original in memory anymore.
27 $this->image->clear();
28 $this->image->destroy();
29 }
30 }
31
32 /**
33 * Checks to see if current environment supports Imagick.
34 *
35 * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
36 * method can be called statically.
37 *
38 * @since 3.5.0
39 *
40 * @param array $args
41 * @return bool
42 */
43 public static function test( $args = array() ) {
44
45 // First, test Imagick's extension and classes.
46 if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick', false ) || ! class_exists( 'ImagickPixel', false ) ) {
47 return false;
48 }
49
50 if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) ) {
51 return false;
52 }
53
54 $required_methods = array(
55 'clear',
56 'destroy',
57 'valid',
58 'getimage',
59 'writeimage',
60 'getimageblob',
61 'getimagegeometry',
62 'getimageformat',
63 'setimageformat',
64 'setimagecompression',
65 'setimagecompressionquality',
66 'setimagepage',
67 'setoption',
68 'scaleimage',
69 'cropimage',
70 'rotateimage',
71 'flipimage',
72 'flopimage',
73 'readimage',
74 'readimageblob',
75 );
76
77 // Now, test for deep requirements within Imagick.
78 if ( ! defined( 'imagick::COMPRESSION_JPEG' ) ) {
79 return false;
80 }
81
82 $class_methods = array_map( 'strtolower', get_class_methods( 'Imagick' ) );
83 if ( array_diff( $required_methods, $class_methods ) ) {
84 return false;
85 }
86
87 return true;
88 }
89
90 /**
91 * Checks to see if editor supports the mime-type specified.
92 *
93 * @since 3.5.0
94 *
95 * @param string $mime_type
96 * @return bool
97 */
98 public static function supports_mime_type( $mime_type ) {
99 $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
100
101 if ( ! $imagick_extension ) {
102 return false;
103 }
104
105 /*
106 * setIteratorIndex is optional unless mime is an animated format.
107 * Here, we just say no if you are missing it and aren't loading a jpeg.
108 */
109 if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && 'image/jpeg' !== $mime_type ) {
110 return false;
111 }
112
113 try {
114 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
115 return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
116 } catch ( Exception $e ) {
117 return false;
118 }
119 }
120
121 /**
122 * Loads image from $this->file into new Imagick Object.
123 *
124 * @since 3.5.0
125 *
126 * @return true|WP_Error True if loaded; WP_Error on failure.
127 */
128 public function load() {
129 if ( $this->image instanceof Imagick ) {
130 return true;
131 }
132
133 if ( ! is_file( $this->file ) && ! wp_is_stream( $this->file ) ) {
134 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file );
135 }
136
137 /*
138 * Even though Imagick uses less PHP memory than GD, set higher limit
139 * for users that have low PHP.ini limits.
140 */
141 wp_raise_memory_limit( 'image' );
142
143 try {
144 $this->image = new Imagick();
145 $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
146
147 if ( 'pdf' === $file_extension ) {
148 $pdf_loaded = $this->pdf_load_source();
149
150 if ( is_wp_error( $pdf_loaded ) ) {
151 return $pdf_loaded;
152 }
153 } else {
154 if ( wp_is_stream( $this->file ) ) {
155 // Due to reports of issues with streams with `Imagick::readImageFile()`, uses `Imagick::readImageBlob()` instead.
156 $this->image->readImageBlob( file_get_contents( $this->file ), $this->file );
157 } else {
158 $this->image->readImage( $this->file );
159 }
160 }
161
162 if ( ! $this->image->valid() ) {
163 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
164 }
165
166 // Select the first frame to handle animated images properly.
167 if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) {
168 $this->image->setIteratorIndex( 0 );
169 }
170
171 if ( 'pdf' === $file_extension ) {
172 $this->remove_pdf_alpha_channel();
173 }
174
175 $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
176 } catch ( Exception $e ) {
177 return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
178 }
179
180 $updated_size = $this->update_size();
181
182 if ( is_wp_error( $updated_size ) ) {
183 return $updated_size;
184 }
185
186 return $this->set_quality();
187 }
188
189 /**
190 * Sets Image Compression quality on a 1-100% scale.
191 *
192 * @since 3.5.0
193 * @since 6.8.0 The `$dims` parameter was added.
194 *
195 * @param int $quality Compression Quality. Range: [1,100]
196 * @param array $dims Optional. Image dimensions array with 'width' and 'height' keys.
197 * @return true|WP_Error True if set successfully; WP_Error on failure.
198 */
199 public function set_quality( $quality = null, $dims = array() ) {
200 $quality_result = parent::set_quality( $quality, $dims );
201 if ( is_wp_error( $quality_result ) ) {
202 return $quality_result;
203 } else {
204 $quality = $this->get_quality();
205 }
206
207 try {
208 switch ( $this->mime_type ) {
209 case 'image/jpeg':
210 $this->image->setImageCompressionQuality( $quality );
211 $this->image->setCompressionQuality( $quality );
212 $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
213 break;
214 case 'image/webp':
215 $webp_info = wp_get_webp_info( $this->file );
216
217 if ( 'lossless' === $webp_info['type'] ) {
218 // Use WebP lossless settings.
219 $this->image->setImageCompressionQuality( 100 );
220 $this->image->setCompressionQuality( 100 );
221 $this->image->setOption( 'webp:lossless', 'true' );
222 parent::set_quality( 100 );
223 } else {
224 $this->image->setImageCompressionQuality( $quality );
225 $this->image->setCompressionQuality( $quality );
226 }
227 break;
228 case 'image/avif':
229 // Set the AVIF encoder to work faster, with minimal impact on image size.
230 $this->image->setOption( 'heic:speed', 7 );
231 $this->image->setImageCompressionQuality( $quality );
232 $this->image->setCompressionQuality( $quality );
233 break;
234 default:
235 $this->image->setImageCompressionQuality( $quality );
236 $this->image->setCompressionQuality( $quality );
237 }
238 } catch ( Exception $e ) {
239 return new WP_Error( 'image_quality_error', $e->getMessage() );
240 }
241 return true;
242 }
243
244
245 /**
246 * Sets or updates current image size.
247 *
248 * @since 3.5.0
249 *
250 * @param int $width
251 * @param int $height
252 * @return true|WP_Error
253 */
254 protected function update_size( $width = null, $height = null ) {
255 $size = null;
256 if ( ! $width || ! $height ) {
257 try {
258 $size = $this->image->getImageGeometry();
259 } catch ( Exception $e ) {
260 return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file );
261 }
262 }
263
264 if ( ! $width ) {
265 $width = $size['width'];
266 }
267
268 if ( ! $height ) {
269 $height = $size['height'];
270 }
271
272 /*
273 * If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF and HEIC images
274 * are properly sized without affecting previous `getImageGeometry` behavior.
275 */
276 if ( ( ! $width || ! $height ) && ( 'image/avif' === $this->mime_type || wp_is_heic_image_mime_type( $this->mime_type ) ) ) {
277 $size = wp_getimagesize( $this->file );
278 $width = $size[0];
279 $height = $size[1];
280 }
281
282 return parent::update_size( $width, $height );
283 }
284
285 /**
286 * Sets Imagick time limit.
287 *
288 * Depending on configuration, Imagick processing may take time.
289 *
290 * Multiple problems exist if PHP times out before ImageMagick completed:
291 * 1. Temporary files aren't cleaned by ImageMagick garbage collection.
292 * 2. No clear error is provided.
293 * 3. The cause of such timeout can be hard to pinpoint.
294 *
295 * This function, which is expected to be run before heavy image routines, resolves
296 * point 1 above by aligning Imagick's timeout with PHP's timeout, assuming it is set.
297 *
298 * However seems it introduces more problems than it fixes,
299 * see https://core.trac.wordpress.org/ticket/58202.
300 *
301 * Note:
302 * - Imagick resource exhaustion does not issue catchable exceptions (yet).
303 * See https://github.com/Imagick/imagick/issues/333.
304 * - The resource limit is not saved/restored. It applies to subsequent
305 * image operations within the time of the HTTP request.
306 *
307 * @since 6.2.0
308 * @deprecated 6.3.0 No longer used in core.
309 *
310 * @return int|null The new limit on success, null on failure.
311 */
312 public static function set_imagick_time_limit() {
313 _deprecated_function( __METHOD__, '6.3.0' );
314
315 if ( ! defined( 'Imagick::RESOURCETYPE_TIME' ) ) {
316 return null;
317 }
318
319 // Returns PHP_FLOAT_MAX if unset.
320 $imagick_timeout = Imagick::getResourceLimit( Imagick::RESOURCETYPE_TIME );
321
322 // Convert to an integer, keeping in mind that: 0 === (int) PHP_FLOAT_MAX.
323 $imagick_timeout = $imagick_timeout > PHP_INT_MAX ? PHP_INT_MAX : (int) $imagick_timeout;
324
325 $php_timeout = (int) ini_get( 'max_execution_time' );
326
327 if ( $php_timeout > 1 && $php_timeout < $imagick_timeout ) {
328 $limit = (float) 0.8 * $php_timeout;
329 Imagick::setResourceLimit( Imagick::RESOURCETYPE_TIME, $limit );
330
331 return $limit;
332 }
333 }
334
335 /**
336 * Resizes current image.
337 *
338 * At minimum, either a height or width must be provided.
339 * If one of the two is set to null, the resize will
340 * maintain aspect ratio according to the provided dimension.
341 *
342 * @since 3.5.0
343 *
344 * @param int|null $max_w Image width.
345 * @param int|null $max_h Image height.
346 * @param bool|array $crop {
347 * Optional. Image cropping behavior. If false, the image will be scaled (default).
348 * If true, image will be cropped to the specified dimensions using center positions.
349 * If an array, the image will be cropped using the array to specify the crop location:
350 *
351 * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
352 * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
353 * }
354 * @return true|WP_Error
355 */
356 public function resize( $max_w, $max_h, $crop = false ) {
357 if ( ( $this->size['width'] === $max_w ) && ( $this->size['height'] === $max_h ) ) {
358 return true;
359 }
360
361 $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
362 if ( ! $dims ) {
363 return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ) );
364 }
365
366 list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
367
368 if ( $crop ) {
369 return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
370 }
371
372 $this->set_quality(
373 null,
374 array(
375 'width' => $dst_w,
376 'height' => $dst_h,
377 )
378 );
379
380 // Execute the resize.
381 $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
382 if ( is_wp_error( $thumb_result ) ) {
383 return $thumb_result;
384 }
385
386 return $this->update_size( $dst_w, $dst_h );
387 }
388
389 /**
390 * Efficiently resize the current image
391 *
392 * This is a WordPress specific implementation of Imagick::thumbnailImage(),
393 * which resizes an image to given dimensions and removes any associated profiles.
394 *
395 * @since 4.5.0
396 *
397 * @param int $dst_w The destination width.
398 * @param int $dst_h The destination height.
399 * @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'.
400 * @param bool $strip_meta Optional. Strip all profiles, excluding color profiles, from the image. Default true.
401 * @return void|WP_Error
402 */
403 protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) {
404 $allowed_filters = array(
405 'FILTER_POINT',
406 'FILTER_BOX',
407 'FILTER_TRIANGLE',
408 'FILTER_HERMITE',
409 'FILTER_HANNING',
410 'FILTER_HAMMING',
411 'FILTER_BLACKMAN',
412 'FILTER_GAUSSIAN',
413 'FILTER_QUADRATIC',
414 'FILTER_CUBIC',
415 'FILTER_CATROM',
416 'FILTER_MITCHELL',
417 'FILTER_LANCZOS',
418 'FILTER_BESSEL',
419 'FILTER_SINC',
420 );
421
422 /**
423 * Set the filter value if '$filter_name' name is in the allowed list and the related
424 * Imagick constant is defined or fall back to the default filter.
425 */
426 if ( in_array( $filter_name, $allowed_filters, true ) && defined( 'Imagick::' . $filter_name ) ) {
427 $filter = constant( 'Imagick::' . $filter_name );
428 } else {
429 $filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false;
430 }
431
432 /**
433 * Filters whether to strip metadata from images when they're resized.
434 *
435 * This filter only applies when resizing using the Imagick editor since GD
436 * always strips profiles by default.
437 *
438 * @since 4.5.0
439 *
440 * @param bool $strip_meta Whether to strip image metadata during resizing. Default true.
441 */
442 if ( apply_filters( 'image_strip_meta', $strip_meta ) ) {
443 $this->strip_meta(); // Fail silently if not supported.
444 }
445
446 try {
447 /**
448 * Special handling for certain types of PNG images:
449 * 1. For PNG images, we need to specify compression settings and remove unneeded chunks.
450 * 2. For indexed PNG images, the number of colors must not exceed 256.
451 * 3. For indexed PNG images with an alpha channel, the tRNS chunk must be preserved.
452 * 4. For indexed PNG images with true alpha transparency (an alpha channel > 1 bit), we need to avoid saving
453 * the image using ImageMagick's 'png8' format, because that supports only binary (1 bit) transparency.
454 *
455 * For #4 we want to check whether the image has a 1-bit alpha channel before resizing, because resizing
456 * may cause the number of alpha values to multiply due to antialiasing. If the original image had only a
457 * 1-bit alpha channel, then a 1-bit alpha channel should be good enough for the resized images.
458 *
459 * Perform all the necessary checks before resizing the image and store the results in variables for later use.
460 */
461 $is_png = false;
462 $is_indexed_png = false;
463 $is_indexed_png_with_alpha_channel = false;
464 $is_indexed_png_with_true_alpha_transparency = false;
465
466 if ( 'image/png' === $this->mime_type ) {
467 $is_png = true;
468
469 if (
470 is_callable( array( $this->image, 'getImageProperty' ) )
471 && '3' === $this->image->getImageProperty( 'png:IHDR.color-type-orig' )
472 ) {
473 $is_indexed_png = true;
474
475 if (
476 is_callable( array( $this->image, 'getImageAlphaChannel' ) )
477 && $this->image->getImageAlphaChannel()
478 ) {
479 $is_indexed_png_with_alpha_channel = true;
480
481 if (
482 is_callable( array( $this->image, 'getImageChannelDepth' ) )
483 && defined( 'Imagick::CHANNEL_ALPHA' )
484 && 1 < $this->image->getImageChannelDepth( Imagick::CHANNEL_ALPHA )
485 ) {
486 $is_indexed_png_with_true_alpha_transparency = true;
487 }
488 }
489 }
490 }
491
492 /*
493 * To be more efficient, resample large images to 5x the destination size before resizing
494 * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111),
495 * unless we would be resampling to a scale smaller than 128x128.
496 */
497 if ( is_callable( array( $this->image, 'sampleImage' ) ) ) {
498 $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] );
499 $sample_factor = 5;
500
501 if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) {
502 $this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor );
503 }
504 }
505
506 /*
507 * Use resizeImage() when it's available and a valid filter value is set.
508 * Otherwise, fall back to the scaleImage() method for resizing, which
509 * results in better image quality over resizeImage() with default filter
510 * settings and retains backward compatibility with pre 4.5 functionality.
511 */
512 if ( is_callable( array( $this->image, 'resizeImage' ) ) && $filter ) {
513 $this->image->setOption( 'filter:support', '2.0' );
514 $this->image->resizeImage( $dst_w, $dst_h, $filter, 1 );
515 } else {
516 $this->image->scaleImage( $dst_w, $dst_h );
517 }
518
519 // Set appropriate quality settings after resizing.
520 if ( 'image/jpeg' === $this->mime_type ) {
521 if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) {
522 $this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 );
523 }
524
525 $this->image->setOption( 'jpeg:fancy-upsampling', 'off' );
526 }
527
528 if ( $is_png ) {
529 $this->image->setOption( 'png:compression-filter', '5' );
530 $this->image->setOption( 'png:compression-level', '9' );
531 $this->image->setOption( 'png:compression-strategy', '1' );
532
533 // Indexed PNG files get some additional handling.
534 // See #63448 for details.
535 if ( $is_indexed_png ) {
536
537 // Check for an alpha channel.
538 if ( $is_indexed_png_with_alpha_channel ) {
539 $this->image->setOption( 'png:include-chunk', 'tRNS' );
540 } else {
541 $this->image->setOption( 'png:exclude-chunk', 'all' );
542 }
543
544 $this->image->quantizeImage( 256, $this->image->getColorspace(), 0, false, false );
545
546 /*
547 * If the colorspace is 'gray', use the png8 format to ensure it stays indexed.
548 * ImageMagick tends to save grayscale images as grayscale PNGs rather than indexed PNGs,
549 * even though grayscale PNGs usually have considerably larger file sizes.
550 * But we can force ImageMagick to save the image as an indexed PNG instead,
551 * by telling it to use png8 format.
552 *
553 * Note that we need to first call quantizeImage() before checking getImageColorspace(),
554 * because only after calling quantizeImage() will the colorspace be COLORSPACE_GRAY for grayscale images
555 * (and we have not found any other way to identify grayscale images).
556 *
557 * We need to avoid forcing indexed format for images with true alpha transparency,
558 * because ImageMagick does not support saving an image with true alpha transparency as an indexed PNG.
559 */
560 if ( Imagick::COLORSPACE_GRAY === $this->image->getImageColorspace() && ! $is_indexed_png_with_true_alpha_transparency ) {
561 // Set the image format to Indexed PNG.
562 $this->image->setOption( 'png:format', 'png8' );
563 }
564 } else {
565 $this->image->setOption( 'png:exclude-chunk', 'all' );
566 }
567 }
568
569 /*
570 * If alpha channel is not defined, set it opaque.
571 *
572 * Note that Imagick::getImageAlphaChannel() is only available if Imagick
573 * has been compiled against ImageMagick version 6.4.0 or newer.
574 */
575 if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) )
576 && is_callable( array( $this->image, 'setImageAlphaChannel' ) )
577 && defined( 'Imagick::ALPHACHANNEL_UNDEFINED' )
578 && defined( 'Imagick::ALPHACHANNEL_OPAQUE' )
579 ) {
580 if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) {
581 $this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE );
582 }
583 }
584
585 // Limit the bit depth of resized images.
586 if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
587 /**
588 * Filters the maximum bit depth of resized images.
589 *
590 * This filter only applies when resizing using the Imagick editor since GD
591 * does not support getting or setting bit depth.
592 *
593 * Use this to adjust the maximum bit depth of resized images.
594 *
595 * @since 6.8.0
596 *
597 * @param int $max_depth The maximum bit depth. Default is the input depth.
598 * @param int $image_depth The bit depth of the original image.
599 */
600 $max_depth = apply_filters( 'image_max_bit_depth', $this->image->getImageDepth(), $this->image->getImageDepth() );
601 $this->image->setImageDepth( $max_depth );
602 }
603 } catch ( Exception $e ) {
604 return new WP_Error( 'image_resize_error', $e->getMessage() );
605 }
606 }
607
608 /**
609 * Create multiple smaller images from a single source.
610 *
611 * Attempts to create all sub-sizes and returns the meta data at the end. This
612 * may result in the server running out of resources. When it fails there may be few
613 * "orphaned" images left over as the meta data is never returned and saved.
614 *
615 * As of 5.3.0 the preferred way to do this is with `make_subsize()`. It creates
616 * the new images one at a time and allows for the meta data to be saved after
617 * each new image is created.
618 *
619 * @since 3.5.0
620 *
621 * @param array $sizes {
622 * An array of image size data arrays.
623 *
624 * Either a height or width must be provided.
625 * If one of the two is set to null, the resize will
626 * maintain aspect ratio according to the provided dimension.
627 *
628 * @type array ...$0 {
629 * Array of height, width values, and whether to crop.
630 *
631 * @type int $width Image width. Optional if `$height` is specified.
632 * @type int $height Image height. Optional if `$width` is specified.
633 * @type bool|array $crop Optional. Whether to crop the image. Default false.
634 * }
635 * }
636 * @return array An array of resized images' metadata by size.
637 */
638 public function multi_resize( $sizes ) {
639 $metadata = array();
640
641 foreach ( $sizes as $size => $size_data ) {
642 $meta = $this->make_subsize( $size_data );
643
644 if ( ! is_wp_error( $meta ) ) {
645 $metadata[ $size ] = $meta;
646 }
647 }
648
649 return $metadata;
650 }
651
652 /**
653 * Create an image sub-size and return the image meta data value for it.
654 *
655 * @since 5.3.0
656 *
657 * @param array $size_data {
658 * Array of size data.
659 *
660 * @type int $width The maximum width in pixels.
661 * @type int $height The maximum height in pixels.
662 * @type bool|array $crop Whether to crop the image to exact dimensions.
663 * }
664 * @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta,
665 * WP_Error object on error.
666 */
667 public function make_subsize( $size_data ) {
668 if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
669 return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) );
670 }
671
672 $orig_size = $this->size;
673 $orig_image = $this->image->getImage();
674
675 if ( ! isset( $size_data['width'] ) ) {
676 $size_data['width'] = null;
677 }
678
679 if ( ! isset( $size_data['height'] ) ) {
680 $size_data['height'] = null;
681 }
682
683 if ( ! isset( $size_data['crop'] ) ) {
684 $size_data['crop'] = false;
685 }
686
687 if ( ( $this->size['width'] === $size_data['width'] ) && ( $this->size['height'] === $size_data['height'] ) ) {
688 return new WP_Error( 'image_subsize_create_error', __( 'The image already has the requested size.' ) );
689 }
690
691 $resized = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
692
693 if ( is_wp_error( $resized ) ) {
694 $saved = $resized;
695 } else {
696 $saved = $this->_save( $this->image );
697
698 $this->image->clear();
699 $this->image->destroy();
700 $this->image = null;
701 }
702
703 $this->size = $orig_size;
704 $this->image = $orig_image;
705
706 if ( ! is_wp_error( $saved ) ) {
707 unset( $saved['path'] );
708 }
709
710 return $saved;
711 }
712
713 /**
714 * Crops Image.
715 *
716 * @since 3.5.0
717 *
718 * @param int $src_x The start x position to crop from.
719 * @param int $src_y The start y position to crop from.
720 * @param int $src_w The width to crop.
721 * @param int $src_h The height to crop.
722 * @param int $dst_w Optional. The destination width.
723 * @param int $dst_h Optional. The destination height.
724 * @param bool $src_abs Optional. If the source crop points are absolute.
725 * @return true|WP_Error
726 */
727 public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
728 if ( $src_abs ) {
729 $src_w -= $src_x;
730 $src_h -= $src_y;
731 }
732
733 try {
734 $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
735 $this->image->setImagePage( $src_w, $src_h, 0, 0 );
736
737 if ( $dst_w || $dst_h ) {
738 /*
739 * If destination width/height isn't specified,
740 * use same as width/height from source.
741 */
742 if ( ! $dst_w ) {
743 $dst_w = $src_w;
744 }
745 if ( ! $dst_h ) {
746 $dst_h = $src_h;
747 }
748
749 $thumb_result = $this->thumbnail_image( $dst_w, $dst_h );
750 if ( is_wp_error( $thumb_result ) ) {
751 return $thumb_result;
752 }
753
754 return $this->update_size();
755 }
756 } catch ( Exception $e ) {
757 return new WP_Error( 'image_crop_error', $e->getMessage() );
758 }
759
760 return $this->update_size();
761 }
762
763 /**
764 * Rotates current image counter-clockwise by $angle.
765 *
766 * @since 3.5.0
767 *
768 * @param float $angle
769 * @return true|WP_Error
770 */
771 public function rotate( $angle ) {
772 /**
773 * $angle is 360-$angle because Imagick rotates clockwise
774 * (GD rotates counter-clockwise)
775 */
776 try {
777 $this->image->rotateImage( new ImagickPixel( 'none' ), 360 - $angle );
778
779 // Normalize EXIF orientation data so that display is consistent across devices.
780 if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
781 $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
782 }
783
784 // Since this changes the dimensions of the image, update the size.
785 $result = $this->update_size();
786 if ( is_wp_error( $result ) ) {
787 return $result;
788 }
789
790 $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
791 } catch ( Exception $e ) {
792 return new WP_Error( 'image_rotate_error', $e->getMessage() );
793 }
794
795 return true;
796 }
797
798 /**
799 * Flips current image.
800 *
801 * @since 3.5.0
802 *
803 * @param bool $horz Flip along Horizontal Axis
804 * @param bool $vert Flip along Vertical Axis
805 * @return true|WP_Error
806 */
807 public function flip( $horz, $vert ) {
808 try {
809 if ( $horz ) {
810 $this->image->flipImage();
811 }
812
813 if ( $vert ) {
814 $this->image->flopImage();
815 }
816
817 // Normalize EXIF orientation data so that display is consistent across devices.
818 if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
819 $this->image->setImageOrientation( Imagick::ORIENTATION_TOPLEFT );
820 }
821 } catch ( Exception $e ) {
822 return new WP_Error( 'image_flip_error', $e->getMessage() );
823 }
824
825 return true;
826 }
827
828 /**
829 * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
830 *
831 * As ImageMagick copies the EXIF data to the flipped/rotated image, proceed only
832 * if EXIF Orientation can be reset afterwards.
833 *
834 * @since 5.3.0
835 *
836 * @return bool|WP_Error True if the image was rotated. False if no EXIF data or if the image doesn't need rotation.
837 * WP_Error if error while rotating.
838 */
839 public function maybe_exif_rotate() {
840 if ( is_callable( array( $this->image, 'setImageOrientation' ) ) && defined( 'Imagick::ORIENTATION_TOPLEFT' ) ) {
841 return parent::maybe_exif_rotate();
842 } else {
843 return new WP_Error( 'write_exif_error', __( 'The image cannot be rotated because the embedded meta data cannot be updated.' ) );
844 }
845 }
846
847 /**
848 * Saves current image to file.
849 *
850 * @since 3.5.0
851 * @since 6.0.0 The `$filesize` value was added to the returned array.
852 *
853 * @param string $destfilename Optional. Destination filename. Default null.
854 * @param string $mime_type Optional. The mime-type. Default null.
855 * @return array|WP_Error {
856 * Array on success or WP_Error if the file failed to save.
857 *
858 * @type string $path Path to the image file.
859 * @type string $file Name of the image file.
860 * @type int $width Image width.
861 * @type int $height Image height.
862 * @type string $mime-type The mime type of the image.
863 * @type int $filesize File size of the image.
864 * }
865 */
866 public function save( $destfilename = null, $mime_type = null ) {
867 $saved = $this->_save( $this->image, $destfilename, $mime_type );
868
869 if ( ! is_wp_error( $saved ) ) {
870 $this->file = $saved['path'];
871 $this->mime_type = $saved['mime-type'];
872
873 try {
874 $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
875 } catch ( Exception $e ) {
876 return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
877 }
878 }
879
880 return $saved;
881 }
882
883 /**
884 * Removes PDF alpha after it's been read.
885 *
886 * @since 6.4.0
887 */
888 protected function remove_pdf_alpha_channel() {
889 $version = Imagick::getVersion();
890 // Remove alpha channel if possible to avoid black backgrounds for Ghostscript >= 9.14. RemoveAlphaChannel added in ImageMagick 6.7.5.
891 if ( $version['versionNumber'] >= 0x675 ) {
892 try {
893 // Imagick::ALPHACHANNEL_REMOVE mapped to RemoveAlphaChannel in PHP imagick 3.2.0b2.
894 $this->image->setImageAlphaChannel( defined( 'Imagick::ALPHACHANNEL_REMOVE' ) ? Imagick::ALPHACHANNEL_REMOVE : 12 );
895 } catch ( Exception $e ) {
896 return new WP_Error( 'pdf_alpha_process_failed', $e->getMessage() );
897 }
898 }
899 }
900
901 /**
902 * @since 3.5.0
903 * @since 6.0.0 The `$filesize` value was added to the returned array.
904 *
905 * @param Imagick $image
906 * @param string $filename
907 * @param string $mime_type
908 * @return array|WP_Error {
909 * Array on success or WP_Error if the file failed to save.
910 *
911 * @type string $path Path to the image file.
912 * @type string $file Name of the image file.
913 * @type int $width Image width.
914 * @type int $height Image height.
915 * @type string $mime-type The mime type of the image.
916 * @type int $filesize File size of the image.
917 * }
918 */
919 protected function _save( $image, $filename = null, $mime_type = null ) {
920 list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
921
922 if ( ! $filename ) {
923 $filename = $this->generate_filename( null, null, $extension );
924 }
925
926 try {
927 // Store initial format.
928 $orig_format = $this->image->getImageFormat();
929
930 $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
931 } catch ( Exception $e ) {
932 return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
933 }
934
935 if ( method_exists( $this->image, 'setInterlaceScheme' )
936 && method_exists( $this->image, 'getInterlaceScheme' )
937 && defined( 'Imagick::INTERLACE_PLANE' )
938 ) {
939 $orig_interlace = $this->image->getInterlaceScheme();
940
941 /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
942 if ( apply_filters( 'image_save_progressive', false, $mime_type ) ) {
943 $this->image->setInterlaceScheme( Imagick::INTERLACE_PLANE ); // True - line interlace output.
944 } else {
945 $this->image->setInterlaceScheme( Imagick::INTERLACE_NO ); // False - no interlace output.
946 }
947 }
948
949 $write_image_result = $this->write_image( $this->image, $filename );
950 if ( is_wp_error( $write_image_result ) ) {
951 return $write_image_result;
952 }
953
954 try {
955 // Reset original format.
956 $this->image->setImageFormat( $orig_format );
957
958 if ( isset( $orig_interlace ) ) {
959 $this->image->setInterlaceScheme( $orig_interlace );
960 }
961 } catch ( Exception $e ) {
962 return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
963 }
964
965 // Set correct file permissions.
966 $stat = stat( dirname( $filename ) );
967 $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits.
968 chmod( $filename, $perms );
969
970 return array(
971 'path' => $filename,
972 /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
973 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
974 'width' => $this->size['width'],
975 'height' => $this->size['height'],
976 'mime-type' => $mime_type,
977 'filesize' => wp_filesize( $filename ),
978 );
979 }
980
981 /**
982 * Writes an image to a file or stream.
983 *
984 * @since 5.6.0
985 *
986 * @param Imagick $image
987 * @param string $filename The destination filename or stream URL.
988 * @return true|WP_Error
989 */
990 private function write_image( $image, $filename ) {
991 if ( wp_is_stream( $filename ) ) {
992 /*
993 * Due to reports of issues with streams with `Imagick::writeImageFile()` and `Imagick::writeImage()`, copies the blob instead.
994 * Checks for exact type due to: https://www.php.net/manual/en/function.file-put-contents.php
995 */
996 if ( file_put_contents( $filename, $image->getImageBlob() ) === false ) {
997 return new WP_Error(
998 'image_save_error',
999 sprintf(
1000 /* translators: %s: PHP function name. */
1001 __( '%s failed while writing image to stream.' ),
1002 '<code>file_put_contents()</code>'
1003 ),
1004 $filename
1005 );
1006 } else {
1007 return true;
1008 }
1009 } else {
1010 $dirname = dirname( $filename );
1011
1012 if ( ! wp_mkdir_p( $dirname ) ) {
1013 return new WP_Error(
1014 'image_save_error',
1015 sprintf(
1016 /* translators: %s: Directory path. */
1017 __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
1018 esc_html( $dirname )
1019 )
1020 );
1021 }
1022
1023 try {
1024 return $image->writeImage( $filename );
1025 } catch ( Exception $e ) {
1026 return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
1027 }
1028 }
1029 }
1030
1031 /**
1032 * Streams current image to browser.
1033 *
1034 * @since 3.5.0
1035 *
1036 * @param string $mime_type The mime type of the image.
1037 * @return true|WP_Error True on success, WP_Error object on failure.
1038 */
1039 public function stream( $mime_type = null ) {
1040 list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
1041
1042 try {
1043 // Temporarily change format for stream.
1044 $this->image->setImageFormat( strtoupper( $extension ) );
1045
1046 // Output stream of image content.
1047 header( "Content-Type: $mime_type" );
1048 print $this->image->getImageBlob();
1049
1050 // Reset image to original format.
1051 $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
1052 } catch ( Exception $e ) {
1053 return new WP_Error( 'image_stream_error', $e->getMessage() );
1054 }
1055
1056 return true;
1057 }
1058
1059 /**
1060 * Strips all image meta except color profiles from an image.
1061 *
1062 * @since 4.5.0
1063 *
1064 * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error.
1065 */
1066 protected function strip_meta() {
1067
1068 if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) {
1069 return new WP_Error(
1070 'image_strip_meta_error',
1071 sprintf(
1072 /* translators: %s: ImageMagick method name. */
1073 __( '%s is required to strip image meta.' ),
1074 '<code>Imagick::getImageProfiles()</code>'
1075 )
1076 );
1077 }
1078
1079 if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) {
1080 return new WP_Error(
1081 'image_strip_meta_error',
1082 sprintf(
1083 /* translators: %s: ImageMagick method name. */
1084 __( '%s is required to strip image meta.' ),
1085 '<code>Imagick::removeImageProfile()</code>'
1086 )
1087 );
1088 }
1089
1090 /*
1091 * Protect a few profiles from being stripped for the following reasons:
1092 *
1093 * - icc: Color profile information
1094 * - icm: Color profile information
1095 * - iptc: Copyright data
1096 * - exif: Orientation data
1097 * - xmp: Rights usage data
1098 */
1099 $protected_profiles = array(
1100 'icc',
1101 'icm',
1102 'iptc',
1103 'exif',
1104 'xmp',
1105 );
1106
1107 try {
1108 // Strip profiles.
1109 foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) {
1110 if ( ! in_array( $key, $protected_profiles, true ) ) {
1111 $this->image->removeImageProfile( $key );
1112 }
1113 }
1114 } catch ( Exception $e ) {
1115 return new WP_Error( 'image_strip_meta_error', $e->getMessage() );
1116 }
1117
1118 return true;
1119 }
1120
1121 /**
1122 * Sets up Imagick for PDF processing.
1123 * Increases rendering DPI and only loads first page.
1124 *
1125 * @since 4.7.0
1126 *
1127 * @return string|WP_Error File to load or WP_Error on failure.
1128 */
1129 protected function pdf_setup() {
1130 try {
1131 /*
1132 * By default, PDFs are rendered in a very low resolution.
1133 * We want the thumbnail to be readable, so increase the rendering DPI.
1134 */
1135 $this->image->setResolution( 128, 128 );
1136
1137 // Only load the first page.
1138 return $this->file . '[0]';
1139 } catch ( Exception $e ) {
1140 return new WP_Error( 'pdf_setup_failed', $e->getMessage(), $this->file );
1141 }
1142 }
1143
1144 /**
1145 * Load the image produced by Ghostscript.
1146 *
1147 * Includes a workaround for a bug in Ghostscript 8.70 that prevents processing of some PDF files
1148 * when `use-cropbox` is set.
1149 *
1150 * @since 5.6.0
1151 *
1152 * @return true|WP_Error
1153 */
1154 protected function pdf_load_source() {
1155 $filename = $this->pdf_setup();
1156
1157 if ( is_wp_error( $filename ) ) {
1158 return $filename;
1159 }
1160
1161 try {
1162 /*
1163 * When generating thumbnails from cropped PDF pages, Imagemagick uses the uncropped
1164 * area (resulting in unnecessary whitespace) unless the following option is set.
1165 */
1166 $this->image->setOption( 'pdf:use-cropbox', true );
1167
1168 /*
1169 * Reading image after Imagick instantiation because `setResolution`
1170 * only applies correctly before the image is read.
1171 */
1172 $this->image->readImage( $filename );
1173 } catch ( Exception $e ) {
1174 // Attempt to run `gs` without the `use-cropbox` option. See #48853.
1175 $this->image->setOption( 'pdf:use-cropbox', false );
1176
1177 $this->image->readImage( $filename );
1178 }
1179
1180 return true;
1181 }
1182}
1183