Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
32.00% covered (danger)
32.00%
40 / 125
33.33% covered (danger)
33.33%
5 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
OptionsTrait
32.00% covered (danger)
32.00%
40 / 125
33.33% covered (danger)
33.33%
5 / 15
772.45
0.00% covered (danger)
0.00%
0 / 1
 log
n/a
0 / 0
n/a
0 / 0
0
 logLn
n/a
0 / 0
n/a
0 / 0
0
 getMimeTypeOfSource
n/a
0 / 0
n/a
0 / 0
0
 getGeneralOptions
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
3
 getUniqueOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createOptions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setProvidedOptions
60.00% covered (warning)
60.00%
12 / 20
0.00% covered (danger)
0.00%
0 / 1
14.18
 getOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOption
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 checkOptions
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
7.46
 logOptions
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getUnsupportedDefaultOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUnsupportedGeneralOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUniqueOptionDefinitions
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getGeneralOptionDefinitions
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getSupportedGeneralOptions
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getSupportedGeneralOptionDefinitions
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getSupportedGeneralOptionIds
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace WebPConvert\Convert\Converters\BaseTraits;
4
5use WebPConvert\Convert\Converters\Stack;
6use WebPConvert\Convert\Exceptions\ConversionFailed\ConversionSkippedException;
7use WebPConvert\Options\Exceptions\InvalidOptionValueException;
8use WebPConvert\Options\Exceptions\InvalidOptionTypeException;
9
10use WebPConvert\Options\GhostOption;
11use WebPConvert\Options\Options;
12use WebPConvert\Options\OptionFactory;
13
14/**
15 * Trait for handling options
16 *
17 * This trait is currently only used in the AbstractConverter class. It has been extracted into a
18 * trait in order to bundle the methods concerning options.
19 *
20 * @package    WebPConvert
21 * @author     Bjørn Rosell <it@rosell.dk>
22 * @since      Class available since Release 2.0.0
23 */
24trait OptionsTrait
25{
26
27    abstract public function log($msg, $style = '');
28    abstract public function logLn($msg, $style = '');
29    abstract protected function getMimeTypeOfSource();
30
31    /** @var array  Provided conversion options (array of simple objects)*/
32    public $providedOptions;
33
34    /** @var array  Calculated conversion options (merge of default options and provided options)*/
35    protected $options;
36
37    /** @var Options  */
38    protected $options2;
39
40    /**
41     *  Get the "general" options (options that are standard in the meaning that they
42     *  are generally available (unless specifically marked as unsupported by a given converter)
43     *
44     *  @param   string   $imageType   (png | jpeg)   The image type - determines the defaults
45     *
46     *  @return  array  Array of options
47     */
48    public function getGeneralOptions($imageType)
49    {
50        $isPng = ($imageType == 'png');
51
52        /*
53        return [
54            //new IntegerOption('auto-limit-adjustment', 5, -100, 100),
55            new BooleanOption('log-call-arguments', false),
56            new BooleanOption('skip', false),
57            new BooleanOption('use-nice', false),
58            new ArrayOption('jpeg', []),
59            new ArrayOption('png', [])
60        ];*/
61
62        $introMd = 'https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/' .
63            'converting/introduction-for-converting.md';
64
65        return OptionFactory::createOptions([
66            ['encoding', 'string', [
67                'title' => 'Encoding',
68                'description' => 'Set encoding for the webp. ' .
69                    'If you choose "auto", webp-convert will ' .
70                    'convert to both lossy and lossless and pick the smallest result',
71                'default' => 'auto',
72                'enum' => ['auto', 'lossy', 'lossless'],
73                'ui' => [
74                    'component' => 'select',
75                    'links' => [['Guide', $introMd . '#auto-selecting-between-losslesslossy-encoding']],
76                ]
77            ]],
78            ['quality', 'int', [
79                'title' => 'Quality (Lossy)',
80                'description' =>
81                    'Quality for lossy encoding. ' .
82                    'In case you enable "auto-limit", you can consider this property a maximum quality.',
83                'default' => ($isPng ? 85 : 75),
84                'default-png' => 85,
85                'default-jpeg' => 75,
86                //'minimum' => 0,
87                //'maximum' => 100,
88                "oneOf" => [
89                    ["type" => "number", "minimum" => 0, 'maximum' => 100],
90                    ["type" => "string", "enum" => ["auto"]]
91                ],
92                'ui' => [
93                    'component' => 'slider',
94                    'display' => "option('encoding') != 'lossless'"
95                ]
96            ]],
97            ['auto-limit', 'boolean', [
98                'title' => 'Auto-limit',
99                'description' =>
100                    'Enable this option to prevent an unnecessarily high quality setting for low ' .
101                    'quality jpegs. It works by adjusting quality setting down to the quality of the jpeg. ' .
102                    'Converting ie a jpeg with quality:50 to ie quality:80 does not get you better quality ' .
103                    'than converting it to quality:80, but it does get you a much bigger file - so you ' .
104                    'really should enable this option.' . "\n\n" .
105                    'The option is ignored for PNG and never adjusts quality up. ' . "\n\n" .
106                    'The feature requires Imagick, ImageMagick or Gmagick in order to detect the quality of ' .
107                    'the jpeg. ' . "\n\n" .
108                    'PS: The "auto-limit" option is relative new. However, before this option, you could achieve ' .
109                    'the same by setting quality to "auto" and specifying a "max-quality" and a "default-quality". ' .
110                    'These are deprecated now, but still works.',
111                'default' => true,
112                'ui' => [
113                    'component' => 'checkbox',
114                    'advanced' => true,
115                    'links' => [
116                        [
117                            'Guide',
118                            $introMd . '#preventing-unnecessarily-high-quality-setting-for-low-quality-jpegs'
119                        ]
120                    ],
121                    'display' => "option('encoding') != 'lossless'"
122                ]
123            ]],
124            ['alpha-quality', 'int', [
125                'title' => 'Alpha quality',
126                'description' =>
127                    'Quality of alpha channel. ' .
128                    'Often, there is no need for high quality transparency layer and in some cases you ' .
129                    'can tweak this all the way down to 10 and save a lot in file size. The option only ' .
130                    'has effect with lossy encoding, and of course only on images with transparency.',
131                'default' => 85,
132                'minimum' => 0,
133                'maximum' => 100,
134                'ui' => [
135                    'component' => 'slider',
136                    'links' => [['Guide', $introMd . '#alpha-quality']],
137                    'display' => "(option('encoding') != 'lossless') && (imageType!='jpeg')"
138                ]
139            ]],
140            ['near-lossless', 'int', [
141                'title' => '"Near lossless" quality',
142                'description' =>
143                    'This option allows you to get impressively better compression for lossless encoding, with ' .
144                    'minimal impact on visual quality. The range is 0 (maximum preprocessing) to 100 (no ' .
145                    'preprocessing). Read the guide for more info.',
146                'default' => 60,
147                'minimum' => 0,
148                'maximum' => 100,
149                'ui' => [
150                    'component' => 'slider',
151                    'links' => [['Guide', $introMd . '#near-lossless']],
152                    'display' => "option('encoding') != 'lossy'"
153                ]
154            ]],
155            ['metadata', 'string', [
156                'title' => 'Metadata',
157                'description' =>
158                    'Determines which metadata that should be copied over to the webp. ' .
159                    'Setting it to "all" preserves all metadata, setting it to "none" strips all metadata. ' .
160                    '*cwebp* can take a comma-separated list of which kinds of metadata that should be copied ' .
161                    '(ie "exif,icc"). *gd* will always remove all metadata and *ffmpeg* will always keep all ' .
162                    'metadata. The rest can either strip all or keep all (they will keep all, unless the option ' .
163                    'is set to *none*)',
164                'default' => 'none',
165                'ui' => [
166                    'component' => 'multi-select',
167                    'options' => ['all', 'none', 'exif', 'icc', 'xmp'],
168                ]
169                // TODO: set regex validation
170            ]],
171            ['method', 'int', [
172                'title' => 'Reduction effort (0-6)',
173                'description' =>
174                    'Controls the trade off between encoding speed and the compressed file size and quality. ' .
175                    'Possible values range from 0 to 6. 0 is fastest. 6 results in best quality and compression. ' .
176                    'PS: The option corresponds to the "method" option in libwebp',
177                'default' => 6,
178                'minimum' => 0,
179                'maximum' => 6,
180                'ui' => [
181                  'component' => 'slider',
182                  'advanced' => true,
183                ]
184            ]],
185            ['sharp-yuv', 'boolean', [
186                'title' => 'Sharp YUV',
187                'description' =>
188                    'Better RGB->YUV color conversion (sharper and more accurate) at the expense of a little extra ' .
189                    'conversion time.',
190                'default' => true,
191                'ui' => [
192                    'component' => 'checkbox',
193                    'advanced' => true,
194                    'links' => [
195                        ['Ctrl.blog', 'https://www.ctrl.blog/entry/webp-sharp-yuv.html'],
196                    ],
197                ]
198            ]],
199            ['auto-filter', 'boolean', [
200                'title' => 'Auto-filter',
201                'description' =>
202                    'Turns auto-filter on. ' .
203                    'This algorithm will spend additional time optimizing the filtering strength to reach a well-' .
204                    'balanced quality. Unfortunately, it is extremely expensive in terms of computation. It takes ' .
205                    'about 5-10 times longer to do a conversion. A 1MB picture which perhaps typically takes about ' .
206                    '2 seconds to convert, will takes about 15 seconds to convert with auto-filter. ',
207                'default' => false,
208                'ui' => [
209                    'component' => 'checkbox',
210                    'advanced' => true,
211                ]
212            ]],
213            ['low-memory', 'boolean', [
214                'title' => 'Low memory',
215                'description' =>
216                    'Reduce memory usage of lossy encoding at the cost of ~30% longer encoding time and marginally ' .
217                    'larger output size. Only effective when the *method* option is 3 or more. Read more in ' .
218                    '[the docs](https://developers.google.com/speed/webp/docs/cwebp)',
219                'default' => false,
220                'ui' => [
221                    'component' => 'checkbox',
222                    'advanced' => true,
223                    'display' => "(option('encoding') != 'lossless') && (option('method')>2)"
224                ]
225            ]],
226            ['preset', 'string', [
227                'title' => 'Preset',
228                'description' =>
229                    'Using a preset will set many of the other options to suit a particular type of ' .
230                    'source material. It even overrides them. It does however not override the quality option. ' .
231                    '"none" means that no preset will be set',
232                'default' => 'none',
233                'enum' => ['none', 'default', 'photo', 'picture', 'drawing', 'icon', 'text'],
234                'ui' => [
235                    'component' => 'select',
236                    'advanced' => true,
237                ]
238            ]],
239            ['size-in-percentage', 'int', ['default' => null, 'minimum' => 0, 'maximum' => 100, 'allow-null' => true]],
240            ['skip', 'boolean', ['default' => false]],
241            ['log-call-arguments', 'boolean', ['default' => false]],
242            // TODO: use-nice should not be a "general" option
243            //['use-nice', 'boolean', ['default' => false]],
244            ['jpeg', 'array', ['default' => []]],
245            ['png', 'array', ['default' => []]],
246
247            // Deprecated options
248            ['default-quality', 'int', [
249                'default' => ($isPng ? 85 : 75),
250                'minimum' => 0,
251                'maximum' => 100,
252                'deprecated' => true]
253            ],
254            ['max-quality', 'int', ['default' => 85, 'minimum' => 0, 'maximum' => 100, 'deprecated' => true]],
255        ]);
256    }
257
258    /**
259     *  Get the unique options for a converter
260     *
261     *  @param   string   $imageType   (png | jpeg)   The image type - determines the defaults
262     *
263     *  @return  array  Array of options
264     */
265    public function getUniqueOptions($imageType)
266    {
267        return [];
268    }
269
270    /**
271     *  Create options.
272     *
273     *  The options created here will be available to all converters.
274     *  Individual converters may add options by overriding this method.
275     *
276     *  @param   string   $imageType   (png | jpeg)   The image type - determines the defaults
277     *
278     *  @return void
279     */
280    protected function createOptions($imageType = 'png')
281    {
282        $this->options2 = new Options();
283        $this->options2->addOptions(... $this->getGeneralOptions($imageType));
284        $this->options2->addOptions(... $this->getUniqueOptions($imageType));
285    }
286
287    /**
288     * Set "provided options" (options provided by the user when calling convert().
289     *
290     * This also calculates the protected options array, by merging in the default options, merging
291     * jpeg and png options and merging prefixed options (such as 'vips-quality').
292     * The resulting options array are set in the protected property $this->options and can be
293     * retrieved using the public ::getOptions() function.
294     *
295     * @param   array $providedOptions (optional)
296     * @return  void
297     */
298    public function setProvidedOptions($providedOptions = [])
299    {
300        $imageType = ($this->getMimeTypeOfSource() == 'image/png' ? 'png' : 'jpeg');
301        $this->createOptions($imageType);
302
303        $this->providedOptions = $providedOptions;
304
305        if (isset($this->providedOptions['png'])) {
306            if ($this->getMimeTypeOfSource() == 'image/png') {
307                $this->providedOptions = array_merge($this->providedOptions, $this->providedOptions['png']);
308//                $this->logLn(print_r($this->providedOptions, true));
309                unset($this->providedOptions['png']);
310            }
311        }
312
313        if (isset($this->providedOptions['jpeg'])) {
314            if ($this->getMimeTypeOfSource() == 'image/jpeg') {
315                $this->providedOptions = array_merge($this->providedOptions, $this->providedOptions['jpeg']);
316                unset($this->providedOptions['jpeg']);
317            }
318        }
319
320        // merge down converter-prefixed options
321        $converterId = self::getConverterId();
322        $strLen = strlen($converterId);
323        foreach ($this->providedOptions as $optionKey => $optionValue) {
324            if (substr($optionKey, 0, $strLen + 1) == ($converterId . '-')) {
325                $this->providedOptions[substr($optionKey, $strLen + 1)] = $optionValue;
326                unset($this->providedOptions[$optionKey]);
327            }
328        }
329
330        // Create options (Option objects)
331        foreach ($this->providedOptions as $optionId => $optionValue) {
332            $this->options2->setOrCreateOption($optionId, $optionValue);
333        }
334        //$this->logLn(print_r($this->options2->getOptions(), true));
335//$this->logLn($this->options2->getOption('hello'));
336
337        // Create flat associative array of options
338        $this->options = $this->options2->getOptions();
339
340        // -  Merge $defaultOptions into provided options
341        //$this->options = array_merge($this->getDefaultOptions(), $this->providedOptions);
342
343        //$this->logOptions();
344    }
345
346    /**
347     * Get the resulting options after merging provided options with default options.
348     *
349     * Note that the defaults depends on the mime type of the source. For example, the default value for quality
350     * is "auto" for jpegs, and 85 for pngs.
351     *
352     * @return array  An associative array of options: ['metadata' => 'none', ...]
353     */
354    public function getOptions()
355    {
356        return $this->options;
357    }
358
359    /**
360     * Change an option specifically.
361     *
362     * This method is probably rarely neeeded. We are using it to change the "encoding" option temporarily
363     * in the EncodingAutoTrait.
364     *
365     * @param  string  $id      Id of option (ie "metadata")
366     * @param  mixed   $value   The new value.
367     * @return void
368     */
369    protected function setOption($id, $value)
370    {
371        $this->options[$id] = $value;
372        $this->options2->setOrCreateOption($id, $value);
373    }
374
375    /**
376     *  Check options.
377     *
378     *  @throws InvalidOptionTypeException   if an option have wrong type
379     *  @throws InvalidOptionValueException  if an option value is out of range
380     *  @throws ConversionSkippedException   if 'skip' option is set to true
381     *  @return void
382     */
383    protected function checkOptions()
384    {
385        $this->options2->check();
386
387        if ($this->options['skip']) {
388            if (($this->getMimeTypeOfSource() == 'image/png') && isset($this->options['png']['skip'])) {
389                throw new ConversionSkippedException(
390                    'skipped conversion (configured to do so for PNG)'
391                );
392            } else {
393                throw new ConversionSkippedException(
394                    'skipped conversion (configured to do so)'
395                );
396            }
397        }
398    }
399
400    public function logOptions()
401    {
402        $this->logLn('');
403        $this->logLn('Options:');
404        $this->logLn('------------');
405
406        $unsupported = $this->getUnsupportedDefaultOptions();
407        $received = [];
408        $implicitlySet = [];
409        foreach ($this->options2->getOptionsMap() as $id => $option) {
410            if (in_array($id, [
411                'png', 'jpeg', '_skip_input_check', '_suppress_success_message', 'skip', 'log_call_arguments'
412            ])) {
413                continue;
414            }
415            if ($option->isValueExplicitlySet()) {
416                $received[] = $option;
417            } else {
418                if (($option instanceof GhostOption) || in_array($id, $unsupported)) {
419                    //$received[] = $option;
420                } else {
421                    if (!$option->isDeprecated()) {
422                        $implicitlySet[] = $option;
423                    }
424                }
425            }
426        }
427
428        if (count($received) > 0) {
429            foreach ($received as $option) {
430                $this->log('- ' . $option->getId() . ': ');
431                if ($option instanceof GhostOption) {
432                    $this->log('  (unknown to ' . $this->getConverterId() . ')', 'bold');
433                    $this->logLn('');
434                    continue;
435                }
436                $this->log($option->getValueForPrint());
437                if ($option->isDeprecated()) {
438                    $this->log(' (deprecated)', 'bold');
439                }
440                if (in_array($option->getId(), $unsupported)) {
441                    if ($this instanceof Stack) {
442                        //$this->log('  *(passed on)*');
443                    } else {
444                        $this->log(' (unsupported by ' . $this->getConverterId() . ')', 'bold');
445                    }
446                }
447                $this->logLn('');
448            }
449            $this->logLn('');
450            $this->logLn(
451                'Note that these are the resulting options after merging down the "jpeg" and "png" options and any ' .
452                'converter-prefixed options'
453            );
454        }
455
456        if (count($implicitlySet) > 0) {
457            $this->logLn('');
458            $this->logLn('Defaults:');
459            $this->logLn('------------');
460            $this->logLn(
461                'The following options was not set, so using the following defaults:'
462            );
463            foreach ($implicitlySet as $option) {
464                $this->log('- ' . $option->getId() . ': ');
465                $this->log($option->getValueForPrint());
466                /*if ($option instanceof GhostOption) {
467                    $this->log('  **(ghost)**');
468                }*/
469                $this->logLn('');
470            }
471        }
472    }
473
474    // to be overridden by converters
475    protected function getUnsupportedDefaultOptions()
476    {
477        return [];
478    }
479
480    public function getUnsupportedGeneralOptions()
481    {
482        return $this->getUnsupportedDefaultOptions();
483    }
484
485    /**
486      * Get unique option definitions.
487      *
488      * Gets definitions of the converters "unique" options (that is, those options that
489      * are not general). It was added in order to give GUI's a way to automatically adjust
490      * their setting screens.
491      *
492      * @param  bool  $filterOutOptionsWithoutUI  If options without UI defined should be filtered out
493      * @param  string   $imageType   (png | jpeg)   The image type - determines the defaults
494      *
495      * @return array  Array of options definitions - ready to be json encoded, or whatever
496      */
497    public function getUniqueOptionDefinitions($filterOutOptionsWithoutUI = true, $imageType = 'jpeg')
498    {
499        $uniqueOptions = new Options();
500        //$uniqueOptions->addOptions(... $this->getUniqueOptions($imageType));
501        foreach ($this->getUniqueOptions($imageType) as $uoption) {
502            $uoption->setId(self::getConverterId() . '-' . $uoption->getId());
503            $uniqueOptions->addOption($uoption);
504        }
505
506        $optionDefinitions = $uniqueOptions->getDefinitions();
507        if ($filterOutOptionsWithoutUI) {
508            $optionDefinitions = array_filter($optionDefinitions, function ($value) {
509                return !is_null($value['ui']);
510            });
511            $optionDefinitions = array_values($optionDefinitions); // re-index
512        }
513        return $optionDefinitions;
514    }
515
516    /**
517     * Get general option definitions.
518     *
519     * Gets definitions of all general options (not just the ones supported by current converter)
520     * For UI's, as a way to automatically adjust their setting screens.
521     *
522     * @param  bool  $filterOutOptionsWithoutUI  If options without UI defined should be filtered out
523     * @param  string   $imageType   (png | jpeg)   The image type - determines the defaults
524     *
525     * @return  array  Array of options definitions - ready to be json encoded, or whatever
526     */
527    public function getGeneralOptionDefinitions($filterOutOptionsWithoutUI = true, $imageType = 'jpeg')
528    {
529        $generalOptions = new Options();
530        $generalOptions->addOptions(... $this->getGeneralOptions($imageType));
531        //$generalOptions->setUI($this->getUIForGeneralOptions($imageType));
532        $optionDefinitions = $generalOptions->getDefinitions();
533        if ($filterOutOptionsWithoutUI) {
534            $optionDefinitions = array_filter($optionDefinitions, function ($value) {
535                return !is_null($value['ui']);
536            });
537            $optionDefinitions = array_values($optionDefinitions); // re-index
538        }
539        return $optionDefinitions;
540    }
541
542    public function getSupportedGeneralOptions($imageType = 'png')
543    {
544        $unsupportedGeneral = $this->getUnsupportedDefaultOptions();
545        $generalOptionsArr = $this->getGeneralOptions($imageType);
546        $supportedIds = [];
547        foreach ($generalOptionsArr as $i => $option) {
548            if (in_array($option->getId(), $unsupportedGeneral)) {
549                unset($generalOptionsArr[$i]);
550            }
551        }
552        return $generalOptionsArr;
553    }
554
555       /**
556        *  Get general option definitions.
557        *
558        *  Gets definitions of the converters "general" options. (that is, those options that
559        *  It was added in order to give GUI's a way to automatically adjust their setting screens.
560        *
561        *  @param   string   $imageType   (png | jpeg)   The image type - determines the defaults
562        *
563        *  @return  array  Array of options definitions - ready to be json encoded, or whatever
564        */
565    public function getSupportedGeneralOptionDefinitions($imageType = 'png')
566    {
567        $generalOptions = new Options();
568        $generalOptions->addOptions(... $this->getSupportedGeneralOptions($imageType));
569        return $generalOptions->getDefinitions();
570    }
571
572    public function getSupportedGeneralOptionIds()
573    {
574        $supportedGeneralOptions = $this->getSupportedGeneralOptions();
575        $supportedGeneralIds = [];
576        foreach ($supportedGeneralOptions as $option) {
577            $supportedGeneralIds[] = $option->getId();
578        }
579        return $supportedGeneralIds;
580    }
581}