• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

timber / timber / 5690593717

pending completion
5690593717

Pull #1617

github

web-flow
Merge f587ceffa into b563d274e
Pull Request #1617: 2.x

4433 of 4433 new or added lines in 57 files covered. (100.0%)

3931 of 4433 relevant lines covered (88.68%)

58.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.46
/src/Image/Operation/Resize.php
1
<?php
2

3
namespace Timber\Image\Operation;
4

5
use Imagick;
6
use Timber\Helper;
7
use Timber\Image\Operation as ImageOperation;
8
use Timber\ImageHelper;
9
use WP_Image_Editor;
10

11
/**
12
 * Changes image to new size, by shrinking/enlarging
13
 * then cropping to respect new ratio.
14
 *
15
 * Arguments:
16
 * - width of new image
17
 * - height of new image
18
 * - crop method
19
 */
20
class Resize extends ImageOperation
21
{
22
    private $w;
23

24
    private $h;
25

26
    private $crop;
27

28
    /**
29
     * @param int    $w    width of new image
30
     * @param int    $h    height of new image
31
     * @param string $crop cropping method, one of: 'default', 'center', 'top', 'bottom', 'left', 'right', 'top-center', 'bottom-center'.
32
     */
33
    public function __construct($w, $h, $crop)
34
    {
35
        $this->w = $w;
56✔
36
        $this->h = $h;
56✔
37
        // Sanitize crop position
38
        $allowed_crop_positions = ['default', 'center', 'top', 'bottom', 'left', 'right', 'top-center', 'bottom-center'];
56✔
39
        if ($crop !== false && !\in_array($crop, $allowed_crop_positions)) {
56✔
40
            $crop = $allowed_crop_positions[0];
3✔
41
        }
42
        $this->crop = $crop;
56✔
43
    }
44

45
    /**
46
     * @param   string    $src_filename     the basename of the file (ex: my-awesome-pic)
47
     * @param   string    $src_extension    the extension (ex: .jpg)
48
     * @return  string    the final filename to be used (ex: my-awesome-pic-300x200-c-default.jpg)
49
     */
50
    public function filename($src_filename, $src_extension)
51
    {
52
        $w = 0;
47✔
53
        $h = 0;
47✔
54
        if ($this->w) {
47✔
55
            $w = $this->w;
46✔
56
        }
57
        if ($this->h) {
47✔
58
            $h = $this->h;
45✔
59
        }
60
        $result = $src_filename . '-' . $w . 'x' . $h . '-c-' . ($this->crop ? $this->crop : 'f'); // Crop will be either user named or f (false)
47✔
61
        if ($src_extension) {
47✔
62
            $result .= '.' . $src_extension;
47✔
63
        }
64
        return $result;
47✔
65
    }
66

67
    /**
68
     * Run a resize as animated GIF (if the server supports it)
69
     *
70
     * @param string           $load_filename the name of the file to resize.
71
     * @param string           $save_filename the desired name of the file to save.
72
     * @param WP_Image_Editor $editor the image editor we're using.
73
     * @return bool
74
     */
75
    protected function run_animated_gif($load_filename, $save_filename, WP_Image_Editor $editor)
76
    {
77
        $w = $this->w;
3✔
78
        $h = $this->h;
3✔
79
        if (!\class_exists('Imagick') || (\defined('TEST_NO_IMAGICK') && TEST_NO_IMAGICK)) {
3✔
80
            Helper::warn('Cannot resize GIF, Imagick is not installed');
1✔
81
            return false;
×
82
        }
83
        $image = new Imagick($load_filename);
2✔
84
        $image = $image->coalesceImages();
2✔
85
        $crop = $this->get_target_sizes($editor);
2✔
86
        foreach ($image as $frame) {
2✔
87
            $frame->cropImage($crop['src_w'], $crop['src_h'], \round($crop['x']), \round($crop['y']));
2✔
88
            $frame->thumbnailImage($w, $h);
2✔
89
            $frame->setImagePage($w, $h, 0, 0);
2✔
90
        }
91
        $image = $image->deconstructImages();
2✔
92
        return $image->writeImages($save_filename, true);
2✔
93
    }
94

95
    /**
96
     * @param WP_Image_Editor $image
97
     */
98
    protected function get_target_sizes(WP_Image_Editor $image)
99
    {
100
        $w = $this->w;
37✔
101
        $h = $this->h;
37✔
102
        $crop = $this->crop;
37✔
103

104
        $current_size = $image->get_size();
37✔
105
        $src_w = $current_size['width'];
37✔
106
        $src_h = $current_size['height'];
37✔
107
        $src_ratio = $src_w / $src_h;
37✔
108
        if (!$h) {
37✔
109
            $h = \round($w / $src_ratio);
2✔
110
        }
111
        if (!$w) {
37✔
112
            //the user wants to resize based on constant height
113
            $w = \round($h * $src_ratio);
1✔
114
        }
115

116
        if (!$crop || $crop === 'default') {
37✔
117
            return [
31✔
118
                'x' => 0,
31✔
119
                'y' => 0,
31✔
120
                'src_w' => $src_w,
31✔
121
                'src_h' => $src_h,
31✔
122
                'target_w' => $w,
31✔
123
                'target_h' => $h,
31✔
124
            ];
31✔
125
        }
126
        // Get ratios
127
        $dest_ratio = $w / $h;
11✔
128
        $src_wt = $src_h * $dest_ratio;
11✔
129
        $src_ht = $src_w / $dest_ratio;
11✔
130
        $src_x = $src_w / 2 - $src_wt / 2;
11✔
131
        $src_y = ($src_h - $src_ht) / 6;
11✔
132
        //now specific overrides based on options:
133
        switch ($crop) {
134
            case 'center':
11✔
135
                // Get source x and y
136
                $src_x = \round(($src_w - $src_wt) / 2);
1✔
137
                $src_y = \round(($src_h - $src_ht) / 2);
1✔
138
                break;
1✔
139

140
            case 'top':
10✔
141
                $src_y = 0;
1✔
142
                break;
1✔
143

144
            case 'bottom':
9✔
145
                $src_y = $src_h - $src_ht;
1✔
146
                break;
1✔
147

148
            case 'top-center':
8✔
149
                $src_y = \round(($src_h - $src_ht) / 4);
1✔
150
                break;
1✔
151

152
            case 'bottom-center':
7✔
153
                $src_y = $src_h - $src_ht - \round(($src_h - $src_ht) / 4);
1✔
154
                break;
1✔
155

156
            case 'left':
6✔
157
                $src_x = 0;
5✔
158
                break;
5✔
159

160
            case 'right':
1✔
161
                $src_x = $src_w - $src_wt;
1✔
162
                break;
1✔
163
        }
164
        // Crop the image
165
        return ($dest_ratio > $src_ratio)
11✔
166
            ? [
9✔
167
                'x' => 0,
9✔
168
                'y' => $src_y,
9✔
169
                'src_w' => $src_w,
9✔
170
                'src_h' => $src_ht,
9✔
171
                'target_w' => $w,
9✔
172
                'target_h' => $h,
9✔
173
            ]
9✔
174
            : [
11✔
175
                'x' => $src_x,
11✔
176
                'y' => 0,
11✔
177
                'src_w' => $src_wt,
11✔
178
                'src_h' => $src_h,
11✔
179
                'target_w' => $w,
11✔
180
                'target_h' => $h,
11✔
181
            ];
11✔
182
    }
183

184
    /**
185
     * Performs the actual image manipulation,
186
     * including saving the target file.
187
     *
188
     * @param  string $load_filename filepath (not URL) to source file
189
     *                               (ex: /src/var/www/wp-content/uploads/my-pic.jpg)
190
     * @param  string $save_filename filepath (not URL) where result file should be saved
191
     *                               (ex: /src/var/www/wp-content/uploads/my-pic-300x200-c-default.jpg)
192
     * @return boolean|null                  true if everything went fine, false otherwise
193
     */
194
    public function run($load_filename, $save_filename)
195
    {
196
        // Attempt to check if SVG.
197
        if (ImageHelper::is_svg($load_filename)) {
39✔
198
            return false;
1✔
199
        }
200
        $image = \wp_get_image_editor($load_filename);
38✔
201
        if (!\is_wp_error($image)) {
38✔
202
            //should be resized by gif resizer
203
            if (ImageHelper::is_animated_gif($load_filename)) {
38✔
204
                //attempt to resize, return if successful proceed if not
205
                $gif = $this->run_animated_gif($load_filename, $save_filename, $image);
3✔
206
                if ($gif) {
2✔
207
                    return true;
2✔
208
                }
209
            }
210

211
            $crop = $this->get_target_sizes($image);
35✔
212
            $image->crop(
35✔
213
                $crop['x'],
35✔
214
                $crop['y'],
35✔
215
                $crop['src_w'],
35✔
216
                $crop['src_h'],
35✔
217
                $crop['target_w'],
35✔
218
                $crop['target_h']
35✔
219
            );
35✔
220
            $quality = \apply_filters('wp_editor_set_quality', 82, 'image/jpeg');
35✔
221
            $image->set_quality($quality);
35✔
222
            $result = $image->save($save_filename);
35✔
223
            if (\is_wp_error($result)) {
35✔
224
                // @codeCoverageIgnoreStart
225
                Helper::error_log('Error resizing image');
226
                Helper::error_log($result);
227
                return false;
228
                // @codeCoverageIgnoreEnd
229
            } else {
230
                return true;
35✔
231
            }
232
        } elseif (isset($image->error_data['error_loading_image'])) {
×
233
            // @codeCoverageIgnoreStart
234
            Helper::error_log('Error loading ' . $image->error_data['error_loading_image']);
235
        } else {
236
            if (!\extension_loaded('gd')) {
237
                Helper::error_log('Can not resize image, please install php-gd');
238
            } else {
239
                Helper::error_log($image);
240
            }
241
            // @codeCoverageIgnoreEnd
242
        }
243

244
        return false;
×
245
    }
246
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc