]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/media/WebP.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / media / WebP.php
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         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 $filename
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          * @param string $filename
122          * @return array Header data array with entries 'compression', 'width' and 'height', where
123          * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'
124          */
125         public static function extractMetadataFromChunks( $chunks, $filename ) {
126                 $vp8Info = [];
127
128                 foreach ( $chunks as $chunk ) {
129                         if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) {
130                                 // Not a chunk containing interesting metadata
131                                 continue;
132                         }
133
134                         $chunkHeader = file_get_contents( $filename, false, null,
135                                 $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
136                         wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
137
138                         switch ( $chunk['fourCC'] ) {
139                                 case 'VP8 ':
140                                         return array_merge( $vp8Info,
141                                                 self::decodeLossyChunkHeader( $chunkHeader ) );
142                                 case 'VP8L':
143                                         return array_merge( $vp8Info,
144                                                 self::decodeLosslessChunkHeader( $chunkHeader ) );
145                                 case 'VP8X':
146                                         $vp8Info = array_merge( $vp8Info,
147                                                 self::decodeExtendedChunkHeader( $chunkHeader ) );
148                                         // Continue looking for other chunks to improve the metadata
149                                         break;
150                         }
151                 }
152                 return $vp8Info;
153         }
154
155         /**
156          * Decodes a lossy chunk header
157          * @param string $header Header string
158          * @return bool|array See WebPHandler::decodeHeader
159          */
160         protected static function decodeLossyChunkHeader( $header ) {
161                 // Bytes 0-3 are 'VP8 '
162                 // Bytes 4-7 are the VP8 stream size
163                 // Bytes 8-10 are the frame tag
164                 // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
165                 $syncCode = substr( $header, 11, 3 );
166                 if ( $syncCode != "\x9D\x01\x2A" ) {
167                         wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
168                                 bin2hex( $syncCode ) . "\n" );
169                         return [];
170                 }
171                 // Bytes 14-17 are image size
172                 $imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
173                 // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
174                 return [
175                         'compression' => 'lossy',
176                         'width' => $imageSize[1] & 0x3FFF,
177                         'height' => $imageSize[2] & 0x3FFF
178                 ];
179         }
180
181         /**
182          * Decodes a lossless chunk header
183          * @param string $header Header string
184          * @return bool|array See WebPHandler::decodeHeader
185          */
186         public static function decodeLosslessChunkHeader( $header ) {
187                 // Bytes 0-3 are 'VP8L'
188                 // Bytes 4-7 are chunk stream size
189                 // Byte 8 is 0x2F called the signature
190                 if ( $header{8} != "\x2F" ) {
191                         wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
192                                 bin2hex( $header{8} ) . "\n" );
193                         return [];
194                 }
195                 // Bytes 9-12 contain the image size
196                 // Bits 0-13 are width-1; bits 15-27 are height-1
197                 $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
198                 return [
199                                 'compression' => 'lossless',
200                                 'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
201                                 'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
202                                                 ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
203                 ];
204         }
205
206         /**
207          * Decodes an extended chunk header
208          * @param string $header Header string
209          * @return bool|array See WebPHandler::decodeHeader
210          */
211         public static function decodeExtendedChunkHeader( $header ) {
212                 // Bytes 0-3 are 'VP8X'
213                 // Byte 4-7 are chunk length
214                 // Byte 8-11 are a flag bytes
215                 $flags = unpack( 'c', substr( $header, 8, 1 ) );
216
217                 // Byte 12-17 are image size (24 bits)
218                 $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
219                 $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
220
221                 return [
222                         'compression' => 'unknown',
223                         'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
224                         'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
225                         'width' => ( $width[1] & 0xFFFFFF ) + 1,
226                         'height' => ( $height[1] & 0xFFFFFF ) + 1
227                 ];
228         }
229
230         public function getImageSize( $file, $path, $metadata = false ) {
231                 if ( $file === null ) {
232                         $metadata = self::getMetadata( $file, $path );
233                 }
234                 if ( $metadata === false && $file instanceof File ) {
235                         $metadata = $file->getMetadata();
236                 }
237
238                 MediaWiki\suppressWarnings();
239                 $metadata = unserialize( $metadata );
240                 MediaWiki\restoreWarnings();
241
242                 if ( $metadata == false ) {
243                         return false;
244                 }
245                 return [ $metadata['width'], $metadata['height'] ];
246         }
247
248         /**
249          * @param File $file
250          * @return bool True, not all browsers support WebP
251          */
252         public function mustRender( $file ) {
253                 return true;
254         }
255
256         /**
257          * @param File $file
258          * @return bool False if we are unable to render this image
259          */
260         public function canRender( $file ) {
261                 if ( self::isAnimatedImage( $file ) ) {
262                         return false;
263                 }
264                 return true;
265         }
266
267         /**
268          * @param File $image
269          * @return bool
270          */
271         public function isAnimatedImage( $image ) {
272                 $ser = $image->getMetadata();
273                 if ( $ser ) {
274                         $metadata = unserialize( $ser );
275                         if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
276                                 return true;
277                         }
278                 }
279
280                 return false;
281         }
282
283         public function canAnimateThumbnail( $file ) {
284                 return false;
285         }
286
287         /**
288          * Render files as PNG
289          *
290          * @param string $ext
291          * @param string $mime
292          * @param array|null $params
293          * @return array
294          */
295         public function getThumbType( $ext, $mime, $params = null ) {
296                 return [ 'png', 'image/png' ];
297         }
298
299         /**
300          * Must use "im" for XCF
301          *
302          * @param string $dstPath
303          * @param bool $checkDstPath
304          * @return string
305          */
306         protected function getScalerType( $dstPath, $checkDstPath = true ) {
307                 return 'im';
308         }
309 }