Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
13.89% |
10 / 72 |
|
22.22% |
2 / 9 |
CRAP | |
0.00% |
0 / 1 |
GraphicsMagick | |
13.89% |
10 / 72 |
|
22.22% |
2 / 9 |
728.35 | |
0.00% |
0 / 1 |
getUnsupportedDefaultOptions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUniqueOptions | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getPath | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
3.58 | |||
isInstalled | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getVersion | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
isWebPDelegateInstalled | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
checkOperationality | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
3.58 | |||
createCommandLineOptions | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
156 | |||
doActualConvert | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | |
3 | namespace WebPConvert\Convert\Converters; |
4 | |
5 | use WebPConvert\Convert\Converters\AbstractConverter; |
6 | use WebPConvert\Convert\Converters\ConverterTraits\EncodingAutoTrait; |
7 | use WebPConvert\Convert\Converters\ConverterTraits\ExecTrait; |
8 | use WebPConvert\Convert\Exceptions\ConversionFailed\ConverterNotOperational\SystemRequirementsNotMetException; |
9 | use WebPConvert\Convert\Exceptions\ConversionFailedException; |
10 | use WebPConvert\Options\OptionFactory; |
11 | use ExecWithFallback\ExecWithFallback; |
12 | |
13 | //use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\TargetNotFoundException; |
14 | |
15 | /** |
16 | * Convert images to webp by calling gmagick binary (gm). |
17 | * |
18 | * @package WebPConvert |
19 | * @author Bjørn Rosell <it@rosell.dk> |
20 | * @since Class available since Release 2.0.0 |
21 | */ |
22 | class GraphicsMagick extends AbstractConverter |
23 | { |
24 | use ExecTrait; |
25 | use EncodingAutoTrait; |
26 | |
27 | protected function getUnsupportedDefaultOptions() |
28 | { |
29 | return [ |
30 | 'near-lossless', |
31 | 'size-in-percentage', |
32 | ]; |
33 | } |
34 | |
35 | /** |
36 | * Get the options unique for this converter |
37 | * |
38 | * @return array Array of options |
39 | */ |
40 | public function getUniqueOptions($imageType) |
41 | { |
42 | return OptionFactory::createOptions([ |
43 | self::niceOption() |
44 | ]); |
45 | } |
46 | |
47 | private function getPath() |
48 | { |
49 | if (defined('WEBPCONVERT_GRAPHICSMAGICK_PATH')) { |
50 | return constant('WEBPCONVERT_GRAPHICSMAGICK_PATH'); |
51 | } |
52 | if (!empty(getenv('WEBPCONVERT_GRAPHICSMAGICK_PATH'))) { |
53 | return getenv('WEBPCONVERT_GRAPHICSMAGICK_PATH'); |
54 | } |
55 | return 'gm'; |
56 | } |
57 | |
58 | public function isInstalled() |
59 | { |
60 | ExecWithFallback::exec($this->getPath() . ' -version 2>&1', $output, $returnCode); |
61 | return ($returnCode == 0); |
62 | } |
63 | |
64 | public function getVersion() |
65 | { |
66 | ExecWithFallback::exec($this->getPath() . ' -version 2>&1', $output, $returnCode); |
67 | if (($returnCode == 0) && isset($output[0])) { |
68 | return preg_replace('#http.*#', '', $output[0]); |
69 | } |
70 | return 'unknown'; |
71 | } |
72 | |
73 | // Check if webp delegate is installed |
74 | public function isWebPDelegateInstalled() |
75 | { |
76 | ExecWithFallback::exec($this->getPath() . ' -version 2>&1', $output, $returnCode); |
77 | foreach ($output as $line) { |
78 | if (preg_match('#WebP.*yes#i', $line)) { |
79 | return true; |
80 | } |
81 | } |
82 | return false; |
83 | } |
84 | |
85 | /** |
86 | * Check (general) operationality of imagack converter executable |
87 | * |
88 | * @throws SystemRequirementsNotMetException if system requirements are not met |
89 | */ |
90 | public function checkOperationality() |
91 | { |
92 | $this->checkOperationalityExecTrait(); |
93 | |
94 | if (!$this->isInstalled()) { |
95 | throw new SystemRequirementsNotMetException('gmagick is not installed'); |
96 | } |
97 | if (!$this->isWebPDelegateInstalled()) { |
98 | throw new SystemRequirementsNotMetException('webp delegate missing'); |
99 | } |
100 | } |
101 | |
102 | /** |
103 | * Build command line options |
104 | * |
105 | * @return string |
106 | */ |
107 | private function createCommandLineOptions() |
108 | { |
109 | // For available webp options, check out: |
110 | // https://github.com/kstep/graphicsmagick/blob/master/coders/webp.c |
111 | |
112 | $commandArguments = []; |
113 | |
114 | /* |
115 | if ($this->isQualityDetectionRequiredButFailing()) { |
116 | // Unlike imagick binary, it seems gmagick binary uses a fixed |
117 | // quality (75) when quality is omitted |
118 | // So we cannot simply omit in order to get same quality as source. |
119 | // But perhaps there is another way? |
120 | // Check out #91 - it is perhaps as easy as this: "-define jpeg:preserve-settings" |
121 | } |
122 | */ |
123 | $commandArguments[] = '-quality ' . escapeshellarg($this->getCalculatedQuality()); |
124 | |
125 | $options = $this->options; |
126 | |
127 | // preset |
128 | if (!is_null($options['preset'])) { |
129 | if ($options['preset'] != 'none') { |
130 | $imageHint = $options['preset']; |
131 | switch ($imageHint) { |
132 | case 'drawing': |
133 | case 'icon': |
134 | case 'text': |
135 | $imageHint = 'graph'; |
136 | $this->logLn( |
137 | 'Note: the preset was mapped to "graph" because graphicsmagick does not support ' . |
138 | '"drawing", "icon" and "text", but grouped these into one option: "graph".' |
139 | ); |
140 | } |
141 | $commandArguments[] = '-define webp:image-hint=' . escapeshellarg($imageHint); |
142 | } |
143 | } |
144 | |
145 | // encoding |
146 | if ($options['encoding'] == 'lossless') { |
147 | // Btw: |
148 | // I am not sure if we should set "quality" for lossless. |
149 | // Quality should not apply to lossless, but my tests shows that it does in some way for gmagick |
150 | // setting it low, you get bad visual quality and small filesize. Setting it high, you get the opposite |
151 | // Some claim it is a bad idea to set quality, but I'm not so sure. |
152 | // https://stackoverflow.com/questions/4228027/ |
153 | // First, I do not just get bigger images when setting quality, as toc777 does. |
154 | // Secondly, the answer is very old and that bad behaviour is probably fixed by now. |
155 | $commandArguments[] = '-define webp:lossless=true'; |
156 | } else { |
157 | $commandArguments[] = '-define webp:lossless=false'; |
158 | } |
159 | |
160 | if ($options['auto-filter'] === true) { |
161 | $commandArguments[] = '-define webp:auto-filter=true'; |
162 | } |
163 | |
164 | if ($options['alpha-quality'] !== 100) { |
165 | $commandArguments[] = '-define webp:alpha-quality=' . strval($options['alpha-quality']); |
166 | } |
167 | |
168 | if ($options['low-memory']) { |
169 | $commandArguments[] = '-define webp:low-memory=true'; |
170 | } |
171 | |
172 | if ($options['sharp-yuv'] === true) { |
173 | $commandArguments[] = '-define webp:use-sharp-yuv=true'; |
174 | } |
175 | |
176 | if ($options['metadata'] == 'none') { |
177 | $commandArguments[] = '-strip'; |
178 | } |
179 | |
180 | $commandArguments[] = '-define webp:method=' . $options['method']; |
181 | |
182 | $commandArguments[] = escapeshellarg($this->source); |
183 | $commandArguments[] = escapeshellarg('webp:' . $this->destination); |
184 | |
185 | return implode(' ', $commandArguments); |
186 | } |
187 | |
188 | protected function doActualConvert() |
189 | { |
190 | //$this->logLn('Using quality:' . $this->getCalculatedQuality()); |
191 | |
192 | $this->logLn('Version: ' . $this->getVersion()); |
193 | |
194 | $command = $this->getPath() . ' convert ' . $this->createCommandLineOptions() . ' 2>&1'; |
195 | |
196 | $useNice = ($this->options['use-nice'] && $this->checkNiceSupport()); |
197 | if ($useNice) { |
198 | $command = 'nice ' . $command; |
199 | } |
200 | $this->logLn('Executing command: ' . $command); |
201 | ExecWithFallback::exec($command, $output, $returnCode); |
202 | |
203 | $this->logExecOutput($output); |
204 | if ($returnCode == 0) { |
205 | $this->logLn('success'); |
206 | } else { |
207 | $this->logLn('return code: ' . $returnCode); |
208 | } |
209 | |
210 | if ($returnCode == 127) { |
211 | throw new SystemRequirementsNotMetException('gmagick is not installed'); |
212 | } |
213 | if ($returnCode != 0) { |
214 | $this->logLn('command:' . $command); |
215 | $this->logLn('return code:' . $returnCode); |
216 | $this->logLn('output:' . print_r(implode("\n", $output), true)); |
217 | throw new SystemRequirementsNotMetException('The exec() call failed'); |
218 | } |
219 | } |
220 | } |