Basic support for WebP
Adds basic image size detection for WebP and support in the MediaHandler. Currently renders WebP files as PNGs, because that handles transparency. Bug: T50519 Change-Id: I3c00653a8a034efc3f6b60fe62b7ac2e5391f921
This commit is contained in:
parent
8be37c1c3d
commit
9c8f333eb8
12 changed files with 543 additions and 3 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1,2 +1,3 @@
|
|||
*.sh eol=lf
|
||||
*.icc binary
|
||||
*.webp binary
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ production.
|
|||
* (T68699) The expiration of the UserID and Token login cookies
|
||||
($wgExtendedLoginCookieExpiration) can be configured independently of the
|
||||
expiration of all other cookies ($wgCookieExpiration).
|
||||
* (bug 50519) Support for generating JPEG/PNG thumbnails from WebP images added
|
||||
if ImageMagick is used as image scaler ($wgUseImageMagick = true). Uploading
|
||||
of WebP images still disabled by default. Add $wgFileExtensions[] =
|
||||
'webp'; to LocalSettings.php to enable uploading of WebP images.
|
||||
|
||||
==== External libraries ====
|
||||
* Update es5-shim from v4.0.0 to v4.1.5.
|
||||
|
|
|
|||
|
|
@ -1040,6 +1040,7 @@ $wgAutoloadLocalClasses = array(
|
|||
'RevisionList' => __DIR__ . '/includes/RevisionList.php',
|
||||
'RevisionListBase' => __DIR__ . '/includes/RevisionList.php',
|
||||
'RevisiondeleteAction' => __DIR__ . '/includes/actions/RevisiondeleteAction.php',
|
||||
'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
|
||||
'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
|
||||
'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
|
||||
'RollbackEdits' => __DIR__ . '/maintenance/rollbackEdits.php',
|
||||
|
|
@ -1342,6 +1343,7 @@ $wgAutoloadLocalClasses = array(
|
|||
'WebInstallerUpgrade' => __DIR__ . '/includes/installer/WebInstallerPage.php',
|
||||
'WebInstallerUpgradeDoc' => __DIR__ . '/includes/installer/WebInstallerPage.php',
|
||||
'WebInstallerWelcome' => __DIR__ . '/includes/installer/WebInstallerPage.php',
|
||||
'WebPHandler' => __DIR__ . '/includes/media/WebP.php',
|
||||
'WebRequest' => __DIR__ . '/includes/WebRequest.php',
|
||||
'WebRequestUpload' => __DIR__ . '/includes/WebRequest.php',
|
||||
'WebResponse' => __DIR__ . '/includes/WebResponse.php',
|
||||
|
|
|
|||
|
|
@ -884,6 +884,7 @@ $wgMediaHandlers = array(
|
|||
'image/png' => 'PNGHandler',
|
||||
'image/gif' => 'GIFHandler',
|
||||
'image/tiff' => 'TiffHandler',
|
||||
'image/webp' => 'WebPHandler',
|
||||
'image/x-ms-bmp' => 'BmpHandler',
|
||||
'image/x-bmp' => 'BmpHandler',
|
||||
'image/x-xcf' => 'XCFHandler',
|
||||
|
|
|
|||
|
|
@ -695,7 +695,7 @@ class MimeMagic {
|
|||
}
|
||||
|
||||
/* Look for WebP */
|
||||
if ( strncmp( $head, "RIFF", 4 ) == 0 && strncmp( substr( $head, 8, 8 ), "WEBPVP8 ", 8 ) == 0 ) {
|
||||
if ( strncmp( $head, "RIFF", 4 ) == 0 && strncmp( substr( $head, 8, 7 ), "WEBPVP8", 7 ) == 0 ) {
|
||||
wfDebug( __METHOD__ . ": recognized file as image/webp\n" );
|
||||
return "image/webp";
|
||||
}
|
||||
|
|
|
|||
100
includes/libs/RiffExtractor.php
Normal file
100
includes/libs/RiffExtractor.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
/**
|
||||
* Extractor for the Resource Interchange File Format
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @author Bryan Tong Minh
|
||||
* @ingroup Media
|
||||
*/
|
||||
|
||||
class RiffExtractor {
|
||||
public static function findChunksFromFile( $filename, $maxChunks = -1 ) {
|
||||
$file = fopen( $filename, 'rb' );
|
||||
$info = self::findChunks( $file, $maxChunks );
|
||||
fclose( $file );
|
||||
return $info;
|
||||
}
|
||||
|
||||
public static function findChunks( $file, $maxChunks = -1 ) {
|
||||
$riff = fread( $file, 4 );
|
||||
if ( $riff !== 'RIFF' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Next four bytes are fileSize
|
||||
$fileSize = fread( $file, 4 );
|
||||
if ( !$fileSize || strlen( $fileSize ) != 4 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Next four bytes are the FourCC
|
||||
$fourCC = fread( $file, 4 );
|
||||
if ( !$fourCC || strlen( $fourCC ) != 4 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create basic info structure
|
||||
$info = array(
|
||||
'fileSize' => self::extractUInt32( $fileSize ),
|
||||
'fourCC' => $fourCC,
|
||||
'chunks' => array(),
|
||||
);
|
||||
$numberOfChunks = 0;
|
||||
|
||||
// Find out the chunks
|
||||
while ( !feof( $file ) && !( $numberOfChunks >= $maxChunks && $maxChunks >= 0 ) ) {
|
||||
$chunkStart = ftell( $file );
|
||||
|
||||
$chunkFourCC = fread( $file, 4 );
|
||||
if ( !$chunkFourCC || strlen( $chunkFourCC ) != 4 ) {
|
||||
return $info;
|
||||
}
|
||||
|
||||
$chunkSize = fread( $file, 4 );
|
||||
if ( !$chunkSize || strlen( $chunkSize ) != 4 ) {
|
||||
return $info;
|
||||
}
|
||||
$intChunkSize = self::extractUInt32( $chunkSize );
|
||||
|
||||
// Add chunk info to the info structure
|
||||
$info['chunks'][] = array(
|
||||
'fourCC' => $chunkFourCC,
|
||||
'start' => $chunkStart,
|
||||
'size' => $intChunkSize
|
||||
);
|
||||
|
||||
// Uneven chunks have padding bytes
|
||||
$padding = $intChunkSize % 2;
|
||||
// Seek to the next chunk
|
||||
fseek( $file, $intChunkSize + $padding, SEEK_CUR );
|
||||
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a little-endian uint32 from a 4 byte string
|
||||
* @param string $string 4-byte string
|
||||
* @return int
|
||||
*/
|
||||
public static function extractUInt32( $string ) {
|
||||
$unpacked = unpack( 'V', $string );
|
||||
return $unpacked[1];
|
||||
}
|
||||
};
|
||||
|
|
@ -93,9 +93,8 @@ class BitmapHandler extends TransformationalImageHandler {
|
|||
// JPEG decoder hint to reduce memory, available since IM 6.5.6-2
|
||||
$decoderHint = array( '-define', "jpeg:size={$params['physicalDimensions']}" );
|
||||
}
|
||||
} elseif ( $params['mimeType'] == 'image/png' ) {
|
||||
} elseif ( $params['mimeType'] == 'image/png' || $params['mimeType'] == 'image/webp' ) {
|
||||
$quality = array( '-quality', '95' ); // zlib 9, adaptive filtering
|
||||
|
||||
} elseif ( $params['mimeType'] == 'image/gif' ) {
|
||||
if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
|
||||
// Extract initial frame only; we're so big it'll
|
||||
|
|
|
|||
306
includes/media/WebP.php
Normal file
306
includes/media/WebP.php
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
<?php
|
||||
/**
|
||||
* Handler for Google's WebP format <https://developers.google.com/speed/webp/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @ingroup Media
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handler for Google's WebP format <https://developers.google.com/speed/webp/>
|
||||
*
|
||||
* @ingroup Media
|
||||
*/
|
||||
class WebPHandler extends BitmapHandler {
|
||||
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
|
||||
/**
|
||||
* @var int Minimum chunk header size to be able to read all header types
|
||||
*/
|
||||
const MINIMUM_CHUNK_HEADER_LENGTH = 18;
|
||||
/**
|
||||
* @var int version of the metadata stored in db records
|
||||
*/
|
||||
const _MW_WEBP_VERSION = 1;
|
||||
|
||||
const VP8X_ICC = 32;
|
||||
const VP8X_ALPHA = 16;
|
||||
const VP8X_EXIF = 8;
|
||||
const VP8X_XMP = 4;
|
||||
const VP8X_ANIM = 2;
|
||||
|
||||
function getMetadata( $image, $filename ) {
|
||||
$parsedWebPData = self::extractMetadata( $filename );
|
||||
if ( !$parsedWebPData ) {
|
||||
return self::BROKEN_FILE;
|
||||
}
|
||||
|
||||
$parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
|
||||
return serialize( $parsedWebPData );
|
||||
}
|
||||
|
||||
function getMetadataType( $image ) {
|
||||
return 'parsed-webp';
|
||||
}
|
||||
|
||||
function isMetadataValid( $image, $metadata ) {
|
||||
if ( $metadata === self::BROKEN_FILE ) {
|
||||
// Do not repetitivly regenerate metadata on broken file.
|
||||
return self::METADATA_GOOD;
|
||||
}
|
||||
|
||||
wfSuppressWarnings();
|
||||
$data = unserialize( $metadata );
|
||||
wfRestoreWarnings();
|
||||
|
||||
if ( !$data || !is_array( $data ) ) {
|
||||
wfDebug( __METHOD__ . " invalid WebP metadata\n" );
|
||||
|
||||
return self::METADATA_BAD;
|
||||
}
|
||||
|
||||
if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] )
|
||||
|| $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
|
||||
) {
|
||||
wfDebug( __METHOD__ . " old but compatible WebP metadata\n" );
|
||||
|
||||
return self::METADATA_COMPATIBLE;
|
||||
}
|
||||
return self::METADATA_GOOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the image size and WebP type from a file
|
||||
*
|
||||
* @param string $chunks Chunks as extracted by RiffExtractor
|
||||
* @return array|bool Header data array with entries 'compression', 'width' and 'height',
|
||||
* where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if
|
||||
* file is not a valid WebP file.
|
||||
*/
|
||||
public static function extractMetadata( $filename ) {
|
||||
wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" );
|
||||
|
||||
$info = RiffExtractor::findChunksFromFile( $filename, 100 );
|
||||
if ( $info === false ) {
|
||||
wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $info['fourCC'] != 'WEBP' ) {
|
||||
wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' .
|
||||
bin2hex( $info['fourCC'] ) . " \n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
$metadata = self::extractMetadataFromChunks( $info['chunks'], $filename );
|
||||
if ( !$metadata ) {
|
||||
wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the image size and WebP type from a file based on the chunk list
|
||||
* @param array $chunks Chunks as extracted by RiffExtractor
|
||||
* @return array Header data array with entries 'compression', 'width' and 'height', where
|
||||
* 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'
|
||||
*/
|
||||
public static function extractMetadataFromChunks( $chunks, $filename ) {
|
||||
$vp8Info = array();
|
||||
|
||||
foreach ( $chunks as $chunk ) {
|
||||
if ( !in_array( $chunk['fourCC'], array( 'VP8 ', 'VP8L', 'VP8X' ) ) ) {
|
||||
// Not a chunk containing interesting metadata
|
||||
continue;
|
||||
}
|
||||
|
||||
$chunkHeader = file_get_contents( $filename, false, null,
|
||||
$chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
|
||||
wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
|
||||
|
||||
switch ( $chunk['fourCC'] ) {
|
||||
case 'VP8 ':
|
||||
return array_merge( $vp8Info,
|
||||
self::decodeLossyChunkHeader( $chunkHeader ) );
|
||||
case 'VP8L':
|
||||
return array_merge( $vp8Info,
|
||||
self::decodeLosslessChunkHeader( $chunkHeader ) );
|
||||
case 'VP8X':
|
||||
$vp8Info = array_merge( $vp8Info,
|
||||
self::decodeExtendedChunkHeader( $chunkHeader ) );
|
||||
// Continue looking for other chunks to improve the metadata
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $vp8Info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a lossy chunk header
|
||||
* @param string $header Header string
|
||||
* @return boolean|array See WebPHandler::decodeHeader
|
||||
*/
|
||||
protected static function decodeLossyChunkHeader( $header ) {
|
||||
// Bytes 0-3 are 'VP8 '
|
||||
// Bytes 4-7 are the VP8 stream size
|
||||
// Bytes 8-10 are the frame tag
|
||||
// Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
|
||||
$syncCode = substr( $header, 11, 3 );
|
||||
if ( $syncCode != "\x9D\x01\x2A" ) {
|
||||
wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
|
||||
bin2hex( $syncCode ) . "\n" );
|
||||
return array();
|
||||
}
|
||||
// Bytes 14-17 are image size
|
||||
$imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
|
||||
// Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
|
||||
return array(
|
||||
'compression' => 'lossy',
|
||||
'width' => $imageSize[1] & 0x3FFF,
|
||||
'height' => $imageSize[2] & 0x3FFF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a lossless chunk header
|
||||
* @param string $header Header string
|
||||
* @return boolean|array See WebPHandler::decodeHeader
|
||||
*/
|
||||
public static function decodeLosslessChunkHeader( $header ) {
|
||||
// Bytes 0-3 are 'VP8L'
|
||||
// Bytes 4-7 are chunk stream size
|
||||
// Byte 8 is 0x2F called the signature
|
||||
if ( $header{8} != "\x2F" ) {
|
||||
wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
|
||||
bin2hex( $header{8} ) . "\n" );
|
||||
return array();
|
||||
}
|
||||
// Bytes 9-12 contain the image size
|
||||
// Bits 0-13 are width-1; bits 15-27 are height-1
|
||||
$imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
|
||||
return array(
|
||||
'compression' => 'lossless',
|
||||
'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
|
||||
'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
|
||||
( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an extended chunk header
|
||||
* @param string $header Header string
|
||||
* @return boolean|array See WebPHandler::decodeHeader
|
||||
*/
|
||||
public static function decodeExtendedChunkHeader( $header ) {
|
||||
// Bytes 0-3 are 'VP8X'
|
||||
// Byte 4-7 are chunk length
|
||||
// Byte 8-11 are a flag bytes
|
||||
$flags = unpack( 'c', substr( $header, 8, 1 ) );
|
||||
|
||||
// Byte 12-17 are image size (24 bits)
|
||||
$width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
|
||||
$height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
|
||||
|
||||
return array(
|
||||
'compression' => 'unknown',
|
||||
'animated' => ($flags[1] & self::VP8X_ANIM) == self::VP8X_ANIM,
|
||||
'transparency' => ($flags[1] & self::VP8X_ALPHA) == self::VP8X_ALPHA,
|
||||
'width' => ( $width[1] & 0xFFFFFF ) + 1,
|
||||
'height' => ( $height[1] & 0xFFFFFF ) + 1
|
||||
);
|
||||
}
|
||||
|
||||
function getImageSize( $file, $path, $metadata = false ) {
|
||||
if ( $file === null ) {
|
||||
$metadata = self::getMetadata( $file, $path );
|
||||
}
|
||||
if ( $metadata === false ) {
|
||||
$metadata = $file->getMetadata();
|
||||
}
|
||||
|
||||
wfSuppressWarnings();
|
||||
$metadata = unserialize( $metadata );
|
||||
wfRestoreWarnings();
|
||||
|
||||
if ( $metadata == false ) {
|
||||
return false;
|
||||
}
|
||||
return array( $metadata['width'], $metadata['height'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $file
|
||||
* @return bool True, not all browsers support WebP
|
||||
*/
|
||||
function mustRender( $file ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $file
|
||||
* @return bool False if we are unable to render this image
|
||||
*/
|
||||
function canRender( $file ) {
|
||||
if ( self::isAnimatedImage( $file ) ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param File $image
|
||||
* @return bool
|
||||
*/
|
||||
function isAnimatedImage( $image ) {
|
||||
$ser = $image->getMetadata();
|
||||
if ( $ser ) {
|
||||
$metadata = unserialize( $ser );
|
||||
if ( isset($metadata['animated']) && $metadata['animated'] === true ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function canAnimateThumbnail( $file ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render files as PNG
|
||||
*
|
||||
* @param $ext
|
||||
* @param $mime
|
||||
* @param $params
|
||||
* @return array
|
||||
*/
|
||||
function getThumbType( $ext, $mime, $params = null ) {
|
||||
return array( 'png', 'image/png' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Must use "im" for XCF
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getScalerType( $dstPath, $checkDstPath = true ) {
|
||||
return 'im';
|
||||
}
|
||||
}
|
||||
BIN
tests/phpunit/data/media/2_webp_a.webp
Normal file
BIN
tests/phpunit/data/media/2_webp_a.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
tests/phpunit/data/media/2_webp_ll.webp
Normal file
BIN
tests/phpunit/data/media/2_webp_ll.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
tests/phpunit/data/media/webp_animated.webp
Normal file
BIN
tests/phpunit/data/media/webp_animated.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 372 KiB |
127
tests/phpunit/includes/media/WebPTest.php
Normal file
127
tests/phpunit/includes/media/WebPTest.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
class WebPHandlerTest extends MediaWikiTestCase {
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
// Allocated file for testing
|
||||
$this->tempFileName = tempnam( wfTempDir(), 'WEBP' );
|
||||
}
|
||||
public function tearDown() {
|
||||
parent::tearDown();
|
||||
unlink( $this->tempFileName );
|
||||
}
|
||||
/**
|
||||
* @dataProvider provideTestExtractMetaData
|
||||
*/
|
||||
public function testExtractMetaData( $header, $expectedResult ) {
|
||||
// Put header into file
|
||||
file_put_contents( $this->tempFileName, $header );
|
||||
|
||||
$this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $this->tempFileName ) );
|
||||
}
|
||||
public function provideTestExtractMetaData() {
|
||||
return array(
|
||||
// Files from https://developers.google.com/speed/webp/gallery2
|
||||
array( "\x52\x49\x46\x46\x90\x68\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x83\x68\x01\x00\x2F\x8F\x01\x4B\x10\x8D\x38\x6C\xDB\x46\x92\xE0\xE0\x82\x7B\x6C",
|
||||
array( 'compression' => 'lossless', 'width' => 400, 'height' => 301 ) ),
|
||||
array( "\x52\x49\x46\x46\x64\x5B\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x8F\x01\x00\x2C\x01\x00\x41\x4C\x50\x48\xE5\x0E",
|
||||
array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 400, 'height' => 301) ),
|
||||
array( "\x52\x49\x46\x46\xA8\x72\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x9B\x72\x00\x00\x2F\x81\x81\x62\x10\x8D\x40\x8C\x24\x39\x6E\x73\x73\x38\x01\x96",
|
||||
array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ),
|
||||
array( "\x52\x49\x46\x46\xE0\x42\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x81\x01\x00\x8A\x01\x00\x41\x4C\x50\x48\x56\x10",
|
||||
array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ),
|
||||
array( "\x52\x49\x46\x46\x70\x61\x02\x00\x57\x45\x42\x50\x56\x50\x38\x4C\x63\x61\x02\x00\x2F\x1F\xC3\x95\x10\x8D\xC8\x72\xDB\xC8\x92\x24\xD8\x91\xD9\x91",
|
||||
array( 'compression' => 'lossless', 'width' => 800, 'height' => 600 ) ),
|
||||
array( "\x52\x49\x46\x46\x1C\x1D\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x1F\x03\x00\x57\x02\x00\x41\x4C\x50\x48\x25\x8B",
|
||||
array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 800, 'height' => 600 ) ),
|
||||
array( "\x52\x49\x46\x46\xFA\xC5\x00\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xEE\xC5\x00\x00\x2F\xA4\x81\x28\x10\x8D\x40\x68\x24\xC9\x91\xA4\xAE\xF3\x97\x75",
|
||||
array( 'compression' => 'lossless', 'width' => 421, 'height' => 163 ) ),
|
||||
array( "\x52\x49\x46\x46\xF6\x5D\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\xA4\x01\x00\xA2\x00\x00\x41\x4C\x50\x48\x38\x1A",
|
||||
array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 421, 'height' => 163 ) ),
|
||||
array( "\x52\x49\x46\x46\xC4\x96\x01\x00\x57\x45\x42\x50\x56\x50\x38\x4C\xB8\x96\x01\x00\x2F\x2B\xC1\x4A\x10\x11\x87\x6D\xDB\x48\x12\xFC\x60\xB0\x83\x24",
|
||||
array( 'compression' => 'lossless', 'width' => 300, 'height' => 300 ) ),
|
||||
array( "\x52\x49\x46\x46\x0A\x11\x01\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x10\x00\x00\x00\x2B\x01\x00\x2B\x01\x00\x41\x4C\x50\x48\x67\x6E",
|
||||
array( 'compression' => 'unknown', 'animated' => false, 'transparency' => true, 'width' => 300, 'height' => 300 ) ),
|
||||
|
||||
// Lossy files from https://developers.google.com/speed/webp/gallery1
|
||||
array( "\x52\x49\x46\x46\x68\x76\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\x5C\x76\x00\x00\xD2\xBE\x01\x9D\x01\x2A\x26\x02\x70\x01\x3E\xD5\x4E\x97\x43\xA2",
|
||||
array( 'compression' => 'lossy', 'width' => 550, 'height' => 368 ) ),
|
||||
array( "\x52\x49\x46\x46\xB0\xEC\x00\x00\x57\x45\x42\x50\x56\x50\x38\x20\xA4\xEC\x00\x00\xB2\x4B\x02\x9D\x01\x2A\x26\x02\x94\x01\x3E\xD1\x50\x96\x46\x26",
|
||||
array( 'compression' => 'lossy', 'width' => 550, 'height' => 404 ) ),
|
||||
array( "\x52\x49\x46\x46\x7A\x19\x03\x00\x57\x45\x42\x50\x56\x50\x38\x20\x6E\x19\x03\x00\xB2\xF8\x09\x9D\x01\x2A\x00\x05\xD0\x02\x3E\xAD\x46\x99\x4A\xA5",
|
||||
array( 'compression' => 'lossy', 'width' => 1280, 'height' => 720 ) ),
|
||||
array( "\x52\x49\x46\x46\x44\xB3\x02\x00\x57\x45\x42\x50\x56\x50\x38\x20\x38\xB3\x02\x00\x52\x57\x06\x9D\x01\x2A\x00\x04\x04\x03\x3E\xA5\x44\x96\x49\x26",
|
||||
array( 'compression' => 'lossy', 'width' => 1024, 'height' => 772) ),
|
||||
array( "\x52\x49\x46\x46\x02\x43\x01\x00\x57\x45\x42\x50\x56\x50\x38\x20\xF6\x42\x01\x00\x12\xC0\x05\x9D\x01\x2A\x00\x04\xF0\x02\x3E\x79\x34\x93\x47\xA4",
|
||||
array( 'compression' => 'lossy', 'width' => 1024, 'height' => 752) ),
|
||||
|
||||
// Animated file from https://groups.google.com/a/chromium.org/d/topic/blink-dev/Y8tRC4mdQz8/discussion
|
||||
array( "\x52\x49\x46\x46\xD0\x0B\x02\x00\x57\x45\x42\x50\x56\x50\x38\x58\x0A\x00\x00\x00\x12\x00\x00\x00\x3F\x01\x00\x3F\x01\x00\x41\x4E",
|
||||
array( 'compression' => 'unknown', 'animated' => true, 'transparency' => true, 'width' => 320, 'height' => 320 ) ),
|
||||
|
||||
// Error cases
|
||||
array( '', false ),
|
||||
array( ' ', false ),
|
||||
array( 'RIFF ', false ),
|
||||
array( 'RIFF1234WEBP ', false ),
|
||||
array( 'RIFF1234WEBPVP8 ', false ),
|
||||
array( 'RIFF1234WEBPVP8L ', false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestWithFileExtractMetaData
|
||||
*/
|
||||
public function testWithFileExtractMetaData( $filename, $expectedResult ) {
|
||||
$this->assertEquals( $expectedResult, WebPHandler::extractMetadata( $filename ) );
|
||||
}
|
||||
public function provideTestWithFileExtractMetaData() {
|
||||
return array(
|
||||
array( __DIR__ . '/../../data/media/2_webp_ll.webp',
|
||||
array( 'compression' => 'lossless', 'width' => 386, 'height' => 395 ) ),
|
||||
array( __DIR__ . '/../../data/media/2_webp_a.webp',
|
||||
array( 'compression' => 'lossy', 'animated' => false, 'transparency' => true, 'width' => 386, 'height' => 395 ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestGetImageSize
|
||||
*/
|
||||
public function testGetImageSize( $path, $expectedResult ) {
|
||||
$handler = new WebPHandler();
|
||||
$this->assertEquals( $expectedResult, $handler->getImageSize( null, $path ) );
|
||||
}
|
||||
public function provideTestGetImageSize() {
|
||||
return array(
|
||||
// Public domain files from https://developers.google.com/speed/webp/gallery2
|
||||
array( __DIR__ . '/../../data/media/2_webp_a.webp', array( 386, 395 ) ),
|
||||
array( __DIR__ . '/../../data/media/2_webp_ll.webp', array( 386, 395 ) ),
|
||||
array( __DIR__ . '/../../data/media/webp_animated.webp', array( 300, 225 ) ),
|
||||
|
||||
// Error cases
|
||||
array( __FILE__, false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the WebP MIME detection. This should really be a separate test, but sticking it
|
||||
* here for now.
|
||||
*
|
||||
* @dataProvider provideTestGetMimeType
|
||||
*/
|
||||
public function testGuessMimeType( $path ) {
|
||||
$mime = MimeMagic::singleton();
|
||||
$this->assertEquals( 'image/webp', $mime->guessMimeType( $path, false ) );
|
||||
}
|
||||
public function provideTestGetMimeType() {
|
||||
return array(
|
||||
// Public domain files from https://developers.google.com/speed/webp/gallery2
|
||||
array( __DIR__ . '/../../data/media/2_webp_a.webp' ),
|
||||
array( __DIR__ . '/../../data/media/2_webp_ll.webp' ),
|
||||
array( __DIR__ . '/../../data/media/webp_animated.webp' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Python code to extract a header and convert to PHP format:
|
||||
* print '"%s"' % ''.join( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) )
|
||||
*/
|
||||
Loading…
Reference in a new issue