Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.44% covered (warning)
74.44%
67 / 90
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Vips
74.44% covered (warning)
74.44%
67 / 90
50.00% covered (danger)
50.00%
4 / 8
57.63
0.00% covered (danger)
0.00%
0 / 1
 getUnsupportedDefaultOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUniqueOptions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 checkOperationality
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
7.48
 checkConvertability
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 createImageResource
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
5.50
 createParamsForVipsWebPSave
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
 webpsave
57.58% covered (warning)
57.58%
19 / 33
0.00% covered (danger)
0.00%
0 / 1
20.24
 doActualConvert
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace WebPConvert\Convert\Converters;
4
5use WebPConvert\Convert\Converters\AbstractConverter;
6use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait;
7use WebPConvert\Convert\Exceptions\ConversionFailedException;
8use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException;
9use WebPConvert\Options\BooleanOption;
10use WebPConvert\Options\IntegerOption;
11
12//require '/home/rosell/.composer/vendor/autoload.php';
13
14/**
15 * Convert images to webp using Vips extension.
16 *
17 * @package    WebPConvert
18 * @author     Bjørn Rosell <it@rosell.dk>
19 * @since      Class available since Release 2.0.0
20 */
21class Vips extends AbstractConverter
22{
23    use EncodingAutoTrait;
24
25    protected function getUnsupportedDefaultOptions()
26    {
27        return [
28            'auto-filter',
29            'size-in-percentage',
30        ];
31    }
32
33    /**
34    *  Get the options unique for this converter
35     *
36     *  @return  array  Array of options
37     */
38    public function getUniqueOptions($imageType)
39    {
40        $ssOption = new BooleanOption('smart-subsample', false);
41        $ssOption->markDeprecated();
42        return [
43            $ssOption
44        ];
45    }
46
47    /**
48     * Check operationality of Vips converter.
49     *
50     * @throws SystemRequirementsNotMetException  if system requirements are not met
51     */
52    public function checkOperationality()
53    {
54        if (!extension_loaded('vips')) {
55            throw new SystemRequirementsNotMetException('Required Vips extension is not available.');
56        }
57
58        if (!function_exists('vips_image_new_from_file')) {
59            throw new SystemRequirementsNotMetException(
60                'Vips extension seems to be installed, however something is not right: ' .
61                'the function "vips_image_new_from_file" is not available.'
62            );
63        }
64
65        if (!function_exists('vips_call')) {
66            throw new SystemRequirementsNotMetException(
67                'Vips extension seems to be installed, however something is not right: ' .
68                'the function "vips_call" is not available.'
69            );
70        }
71
72        if (!function_exists('vips_error_buffer')) {
73            throw new SystemRequirementsNotMetException(
74                'Vips extension seems to be installed, however something is not right: ' .
75                'the function "vips_error_buffer" is not available.'
76            );
77        }
78
79
80        vips_error_buffer(); // clear error buffer
81        $result = vips_call('webpsave', null);
82        if ($result === -1) {
83            $message = vips_error_buffer();
84            if (strpos($message, 'VipsOperation: class "webpsave" not found') === 0) {
85                throw new SystemRequirementsNotMetException(
86                    'Vips has not been compiled with webp support.'
87                );
88            }
89        }
90    }
91
92    /**
93     * Check if specific file is convertable with current converter / converter settings.
94     *
95     * @throws SystemRequirementsNotMetException  if Vips does not support image type
96     */
97    public function checkConvertability()
98    {
99        // It seems that png and jpeg are always supported by Vips
100        // - so nothing needs to be done here
101
102        if (function_exists('vips_version')) {
103            $this->logLn('vipslib version: ' . vips_version());
104        }
105        $this->logLn('vips extension version: ' . phpversion('vips'));
106    }
107
108    /**
109     * Create vips image resource from source file
110     *
111     * @throws  ConversionFailedException  if image resource cannot be created
112     * @return  resource  vips image resource
113     */
114    private function createImageResource()
115    {
116        // We are currently using vips_image_new_from_file(), but we could consider
117        // calling vips_jpegload / vips_pngload instead
118        $result = /** @scrutinizer ignore-call */ vips_image_new_from_file($this->source, []);
119        if ($result === -1) {
120            /*throw new ConversionFailedException(
121                'Failed creating new vips image from file: ' . $this->source
122            );*/
123            $message = /** @scrutinizer ignore-call */ vips_error_buffer();
124            throw new ConversionFailedException($message);
125        }
126
127        if (!is_array($result)) {
128            throw new ConversionFailedException(
129                'vips_image_new_from_file did not return an array, which we expected'
130            );
131        }
132
133        if (count($result) != 1) {
134            throw new ConversionFailedException(
135                'vips_image_new_from_file did not return an array of length 1 as we expected ' .
136                '- length was: ' . count($result)
137            );
138        }
139
140        $im = array_shift($result);
141        return $im;
142    }
143
144    /**
145     * Create parameters for webpsave
146     *
147     * @return  array  the parameters as an array
148     */
149    private function createParamsForVipsWebPSave()
150    {
151        // webpsave options are described here:
152        // https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave
153        // near_lossless option is described here: https://github.com/libvips/libvips/pull/430
154
155        // NOTE: When a new option becomes available, we MUST remember to add
156        //       it to the array of possibly unsupported options in webpsave() !
157        $options = [
158            "Q" => $this->getCalculatedQuality(),
159            'lossless' => ($this->options['encoding'] == 'lossless'),
160            'strip' => $this->options['metadata'] == 'none',
161        ];
162
163        // Only set the following options if they differ from the default of vipslib
164        // This ensures we do not get warning if that property isn't supported
165        if ($this->options['smart-subsample'] !== false) {
166            // PS: The smart-subsample option is now deprecated, as it turned out
167            // it was corresponding to the "sharp-yuv" option (see #280)
168            $options['smart_subsample'] = $this->options['smart-subsample'];
169            $this->logLn(
170                '*Note: the "smart-subsample" option is now deprecated. It turned out it corresponded to ' .
171                'the general option "sharp-yuv". You should use "sharp-yuv" instead.*'
172            );
173        }
174        if ($this->options['sharp-yuv'] !== false) {
175            $options['smart_subsample'] = $this->options['sharp-yuv'];
176        }
177
178        if ($this->options['alpha-quality'] !== 100) {
179            $options['alpha_q'] = $this->options['alpha-quality'];
180        }
181
182        if (!is_null($this->options['preset']) && ($this->options['preset'] != 'none')) {
183            // preset. 0:default, 1:picture, 2:photo, 3:drawing, 4:icon, 5:text, 6:last
184            $options['preset'] = array_search(
185                $this->options['preset'],
186                ['default', 'picture', 'photo', 'drawing', 'icon', 'text']
187            );
188        }
189        if ($this->options['near-lossless'] !== 100) {
190            if ($this->options['encoding'] == 'lossless') {
191                // We only let near_lossless have effect when encoding is set to lossless
192                // otherwise encoding=auto would not work as expected
193                // Available in https://github.com/libvips/libvips/pull/430, merged 1 may 2016
194                // seems it corresponds to release 8.4.2
195                $options['near_lossless'] = true;
196
197                // In Vips, the near-lossless value is controlled by Q.
198                // this differs from how it is done in cwebp, where it is an integer.
199                // We have chosen same option syntax as cwebp
200                $options['Q'] = $this->options['near-lossless'];
201            }
202        }
203        if ($this->options['method'] !== 4) {
204            $options['reduction_effort'] = $this->options['method'];
205        }
206
207        return $options;
208    }
209
210    /**
211     * Save as webp, using vips extension.
212     *
213     * Tries to save image resource as webp, using the supplied options.
214     * Vips fails when a parameter is not supported, but we detect this and unset that parameter and try again
215     * (recursively call itself until there is no more of these kind of errors).
216     *
217     * @param  resource  $im  A vips image resource to save
218     * @throws  ConversionFailedException  if conversion fails.
219     */
220    private function webpsave($im, $options)
221    {
222        /** @scrutinizer ignore-call */ vips_error_buffer(); // clear error buffer
223        $result = /** @scrutinizer ignore-call */ vips_call('webpsave', $im, $this->destination, $options);
224
225        //trigger_error('test-warning', E_USER_WARNING);
226        if ($result === -1) {
227            $message = /** @scrutinizer ignore-call */ vips_error_buffer();
228
229            $nameOfPropertyNotFound = '';
230            if (preg_match("#no property named .(.*).#", $message, $matches)) {
231                $nameOfPropertyNotFound = $matches[1];
232            } elseif (preg_match("#(.*)\\sunsupported$#", $message, $matches)) {
233                // Actually, I am not quite sure if this ever happens.
234                // I got a "near_lossless unsupported" error message in a build, but perhaps it rather a warning
235                if (in_array($matches[1], [
236                    'lossless',
237                    'alpha_q',
238                    'near_lossless',
239                    'smart_subsample',
240                    'reduction_effort',
241                    'preset'
242                ])) {
243                    $nameOfPropertyNotFound = $matches[1];
244                }
245            }
246
247            if ($nameOfPropertyNotFound != '') {
248                $msg = 'Note: Your version of vipslib does not support the "' .
249                    $nameOfPropertyNotFound . '" property';
250
251                switch ($nameOfPropertyNotFound) {
252                    case 'alpha_q':
253                        $msg .= ' (It was introduced in vips 8.4)';
254                        break;
255                    case 'near_lossless':
256                        $msg .= ' (It was introduced in vips 8.4)';
257                        break;
258                    case 'smart_subsample':
259                        $msg .= ' (its the vips equalent to the "sharp-yuv" option. It was introduced in vips 8.4)';
260                        break;
261                    case 'reduction_effort':
262                        $msg .= ' (its the vips equalent to the "method" option. It was introduced in vips 8.8.0)';
263                        break;
264                    case 'preset':
265                        $msg .= ' (It was introduced in vips 8.4)';
266                        break;
267                }
268                $msg .= '. The option is ignored.';
269
270
271                $this->logLn($msg, 'bold');
272
273                unset($options[$nameOfPropertyNotFound]);
274                $this->webpsave($im, $options);
275            } else {
276                throw new ConversionFailedException($message);
277            }
278        }
279    }
280
281    /**
282     * Convert, using vips extension.
283     *
284     * Tries to create image resource and save it as webp using the calculated options.
285     * PS: The Vips "webpsave" call fails when a parameter is not supported, but our webpsave() method
286     * detect this and unset that parameter and try again (repeat until success).
287     *
288     * @throws  ConversionFailedException  if conversion fails.
289     */
290    protected function doActualConvert()
291    {
292/*
293        $im = \Jcupitt\Vips\Image::newFromFile($this->source);
294        //$im->writeToFile(__DIR__ . '/images/small-vips.webp', ["Q" => 10]);
295
296        $im->webpsave($this->destination, [
297            "Q" => 80,
298            //'near_lossless' => true
299        ]);
300        return;*/
301
302        $im = $this->createImageResource();
303        $options = $this->createParamsForVipsWebPSave();
304        $this->webpsave($im, $options);
305    }
306}