These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Handler for Google's WebP format <https://developers.google.com/speed/webp/> |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup Media |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * Handler for Google's WebP format <https://developers.google.com/speed/webp/> |
||
26 | * |
||
27 | * @ingroup Media |
||
28 | */ |
||
29 | class WebPHandler extends BitmapHandler { |
||
30 | const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata. |
||
31 | /** |
||
32 | * @var int Minimum chunk header size to be able to read all header types |
||
33 | */ |
||
34 | const MINIMUM_CHUNK_HEADER_LENGTH = 18; |
||
35 | /** |
||
36 | * @var int version of the metadata stored in db records |
||
37 | */ |
||
38 | const _MW_WEBP_VERSION = 1; |
||
39 | |||
40 | const VP8X_ICC = 32; |
||
41 | const VP8X_ALPHA = 16; |
||
42 | const VP8X_EXIF = 8; |
||
43 | const VP8X_XMP = 4; |
||
44 | const VP8X_ANIM = 2; |
||
45 | |||
46 | public function getMetadata( $image, $filename ) { |
||
47 | $parsedWebPData = self::extractMetadata( $filename ); |
||
48 | if ( !$parsedWebPData ) { |
||
49 | return self::BROKEN_FILE; |
||
50 | } |
||
51 | |||
52 | $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION; |
||
53 | return serialize( $parsedWebPData ); |
||
54 | } |
||
55 | |||
56 | public function getMetadataType( $image ) { |
||
57 | return 'parsed-webp'; |
||
58 | } |
||
59 | |||
60 | View Code Duplication | public function isMetadataValid( $image, $metadata ) { |
|
61 | if ( $metadata === self::BROKEN_FILE ) { |
||
62 | // Do not repetitivly regenerate metadata on broken file. |
||
63 | return self::METADATA_GOOD; |
||
64 | } |
||
65 | |||
66 | MediaWiki\suppressWarnings(); |
||
67 | $data = unserialize( $metadata ); |
||
68 | MediaWiki\restoreWarnings(); |
||
69 | |||
70 | if ( !$data || !is_array( $data ) ) { |
||
71 | wfDebug( __METHOD__ . " invalid WebP metadata\n" ); |
||
72 | |||
73 | return self::METADATA_BAD; |
||
74 | } |
||
75 | |||
76 | if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] ) |
||
77 | || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION |
||
78 | ) { |
||
79 | wfDebug( __METHOD__ . " old but compatible WebP metadata\n" ); |
||
80 | |||
81 | return self::METADATA_COMPATIBLE; |
||
82 | } |
||
83 | return self::METADATA_GOOD; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Extracts the image size and WebP type from a file |
||
88 | * |
||
89 | * @param string $chunks Chunks as extracted by RiffExtractor |
||
0 ignored issues
–
show
|
|||
90 | * @return array|bool Header data array with entries 'compression', 'width' and 'height', |
||
91 | * where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if |
||
92 | * file is not a valid WebP file. |
||
93 | */ |
||
94 | public static function extractMetadata( $filename ) { |
||
95 | wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" ); |
||
96 | |||
97 | $info = RiffExtractor::findChunksFromFile( $filename, 100 ); |
||
98 | if ( $info === false ) { |
||
99 | wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" ); |
||
100 | return false; |
||
101 | } |
||
102 | |||
103 | if ( $info['fourCC'] != 'WEBP' ) { |
||
104 | wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' . |
||
105 | bin2hex( $info['fourCC'] ) . " \n" ); |
||
106 | return false; |
||
107 | } |
||
108 | |||
109 | $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename ); |
||
110 | if ( !$metadata ) { |
||
111 | wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" ); |
||
112 | return false; |
||
113 | } |
||
114 | |||
115 | return $metadata; |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * Extracts the image size and WebP type from a file based on the chunk list |
||
120 | * @param array $chunks Chunks as extracted by RiffExtractor |
||
121 | * @return array Header data array with entries 'compression', 'width' and 'height', where |
||
122 | * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown' |
||
123 | */ |
||
124 | public static function extractMetadataFromChunks( $chunks, $filename ) { |
||
125 | $vp8Info = []; |
||
126 | |||
127 | foreach ( $chunks as $chunk ) { |
||
128 | if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) { |
||
129 | // Not a chunk containing interesting metadata |
||
130 | continue; |
||
131 | } |
||
132 | |||
133 | $chunkHeader = file_get_contents( $filename, false, null, |
||
134 | $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH ); |
||
135 | wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" ); |
||
136 | |||
137 | switch ( $chunk['fourCC'] ) { |
||
138 | case 'VP8 ': |
||
139 | return array_merge( $vp8Info, |
||
140 | self::decodeLossyChunkHeader( $chunkHeader ) ); |
||
141 | case 'VP8L': |
||
142 | return array_merge( $vp8Info, |
||
143 | self::decodeLosslessChunkHeader( $chunkHeader ) ); |
||
144 | case 'VP8X': |
||
145 | $vp8Info = array_merge( $vp8Info, |
||
146 | self::decodeExtendedChunkHeader( $chunkHeader ) ); |
||
147 | // Continue looking for other chunks to improve the metadata |
||
148 | break; |
||
149 | } |
||
150 | } |
||
151 | return $vp8Info; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Decodes a lossy chunk header |
||
156 | * @param string $header Header string |
||
157 | * @return bool|array See WebPHandler::decodeHeader |
||
158 | */ |
||
159 | protected static function decodeLossyChunkHeader( $header ) { |
||
160 | // Bytes 0-3 are 'VP8 ' |
||
161 | // Bytes 4-7 are the VP8 stream size |
||
162 | // Bytes 8-10 are the frame tag |
||
163 | // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code |
||
164 | $syncCode = substr( $header, 11, 3 ); |
||
165 | if ( $syncCode != "\x9D\x01\x2A" ) { |
||
166 | wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' . |
||
167 | bin2hex( $syncCode ) . "\n" ); |
||
168 | return []; |
||
169 | } |
||
170 | // Bytes 14-17 are image size |
||
171 | $imageSize = unpack( 'v2', substr( $header, 14, 4 ) ); |
||
172 | // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here |
||
173 | return [ |
||
174 | 'compression' => 'lossy', |
||
175 | 'width' => $imageSize[1] & 0x3FFF, |
||
176 | 'height' => $imageSize[2] & 0x3FFF |
||
177 | ]; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Decodes a lossless chunk header |
||
182 | * @param string $header Header string |
||
183 | * @return bool|array See WebPHandler::decodeHeader |
||
184 | */ |
||
185 | public static function decodeLosslessChunkHeader( $header ) { |
||
186 | // Bytes 0-3 are 'VP8L' |
||
187 | // Bytes 4-7 are chunk stream size |
||
188 | // Byte 8 is 0x2F called the signature |
||
189 | if ( $header{8} != "\x2F" ) { |
||
190 | wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' . |
||
191 | bin2hex( $header{8} ) . "\n" ); |
||
192 | return []; |
||
193 | } |
||
194 | // Bytes 9-12 contain the image size |
||
195 | // Bits 0-13 are width-1; bits 15-27 are height-1 |
||
196 | $imageSize = unpack( 'C4', substr( $header, 9, 4 ) ); |
||
197 | return [ |
||
198 | 'compression' => 'lossless', |
||
199 | 'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1, |
||
200 | 'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) | |
||
201 | ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1 |
||
202 | ]; |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * Decodes an extended chunk header |
||
207 | * @param string $header Header string |
||
208 | * @return bool|array See WebPHandler::decodeHeader |
||
209 | */ |
||
210 | public static function decodeExtendedChunkHeader( $header ) { |
||
211 | // Bytes 0-3 are 'VP8X' |
||
212 | // Byte 4-7 are chunk length |
||
213 | // Byte 8-11 are a flag bytes |
||
214 | $flags = unpack( 'c', substr( $header, 8, 1 ) ); |
||
215 | |||
216 | // Byte 12-17 are image size (24 bits) |
||
217 | $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" ); |
||
218 | $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" ); |
||
219 | |||
220 | return [ |
||
221 | 'compression' => 'unknown', |
||
222 | 'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM, |
||
223 | 'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA, |
||
224 | 'width' => ( $width[1] & 0xFFFFFF ) + 1, |
||
225 | 'height' => ( $height[1] & 0xFFFFFF ) + 1 |
||
226 | ]; |
||
227 | } |
||
228 | |||
229 | public function getImageSize( $file, $path, $metadata = false ) { |
||
230 | if ( $file === null ) { |
||
231 | $metadata = self::getMetadata( $file, $path ); |
||
232 | } |
||
233 | if ( $metadata === false && $file instanceof File ) { |
||
234 | $metadata = $file->getMetadata(); |
||
235 | } |
||
236 | |||
237 | MediaWiki\suppressWarnings(); |
||
238 | $metadata = unserialize( $metadata ); |
||
239 | MediaWiki\restoreWarnings(); |
||
240 | |||
241 | if ( $metadata == false ) { |
||
242 | return false; |
||
243 | } |
||
244 | return [ $metadata['width'], $metadata['height'] ]; |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * @param $file |
||
249 | * @return bool True, not all browsers support WebP |
||
250 | */ |
||
251 | public function mustRender( $file ) { |
||
252 | return true; |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * @param $file |
||
257 | * @return bool False if we are unable to render this image |
||
258 | */ |
||
259 | public function canRender( $file ) { |
||
260 | if ( self::isAnimatedImage( $file ) ) { |
||
261 | return false; |
||
262 | } |
||
263 | return true; |
||
264 | } |
||
265 | |||
266 | /** |
||
267 | * @param File $image |
||
268 | * @return bool |
||
269 | */ |
||
270 | View Code Duplication | public function isAnimatedImage( $image ) { |
|
271 | $ser = $image->getMetadata(); |
||
272 | if ( $ser ) { |
||
273 | $metadata = unserialize( $ser ); |
||
274 | if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) { |
||
275 | return true; |
||
276 | } |
||
277 | } |
||
278 | |||
279 | return false; |
||
280 | } |
||
281 | |||
282 | public function canAnimateThumbnail( $file ) { |
||
283 | return false; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Render files as PNG |
||
288 | * |
||
289 | * @param $ext |
||
290 | * @param $mime |
||
291 | * @param $params |
||
292 | * @return array |
||
293 | */ |
||
294 | public function getThumbType( $ext, $mime, $params = null ) { |
||
295 | return [ 'png', 'image/png' ]; |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Must use "im" for XCF |
||
300 | * |
||
301 | * @return string |
||
302 | */ |
||
303 | protected function getScalerType( $dstPath, $checkDstPath = true ) { |
||
304 | return 'im'; |
||
305 | } |
||
306 | } |
||
307 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.