From e5343317b8fed849f34fd908f764f691bc58f522 Mon Sep 17 00:00:00 2001 From: Andrew Garrett Date: Mon, 3 Aug 2009 15:01:51 +0000 Subject: [PATCH] (bug 16451) Fix application of scaling limits for animated GIFs. * Creates a new media handler for GIF files, and extracts metadata such as duration, whether or not the GIF is looped, and the number of frames. * Uses the extracted metadata for the long file description. * Considers number of frames in the calculation of image area (multiplies by width and height to get the "time-area", or so to speak). After this patch is deployed, the work-around to disable GIF scaling on Wikimedia sites can be eliminated. --- includes/AutoLoader.php | 2 + includes/DefaultSettings.php | 2 +- includes/media/Bitmap.php | 9 +- includes/media/GIF.php | 58 ++++++++ includes/media/GIFMetadataExtractor.php | 171 ++++++++++++++++++++++++ languages/messages/MessagesEn.php | 2 + 6 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 includes/media/GIF.php create mode 100644 includes/media/GIFMetadataExtractor.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 39db06e985..ef2bda1e37 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -87,6 +87,8 @@ $wgAutoloadLocalClasses = array( 'ForkController' => 'includes/ForkController.php', 'FormatExif' => 'includes/Exif.php', 'FormOptions' => 'includes/FormOptions.php', + 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php', + 'GIFHandler' => 'includes/media/GIF.php', 'GlobalDependency' => 'includes/CacheDependency.php', 'HashBagOStuff' => 'includes/BagOStuff.php', 'HashtableReplacer' => 'includes/StringUtils.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index fe142ae5fd..d16d97aaf3 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2129,7 +2129,7 @@ $wgSiteNotice = ''; $wgMediaHandlers = array( 'image/jpeg' => 'BitmapHandler', 'image/png' => 'BitmapHandler', - 'image/gif' => 'BitmapHandler', + 'image/gif' => 'GIFHandler', 'image/tiff' => 'TiffHandler', 'image/x-ms-bmp' => 'BmpHandler', 'image/x-bmp' => 'BmpHandler', diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php index 7144ebd197..d4a7c35f89 100644 --- a/includes/media/Bitmap.php +++ b/includes/media/Bitmap.php @@ -22,7 +22,7 @@ class BitmapHandler extends ImageHandler { # JPEG has the handy property of allowing thumbnailing without full decompression, so we make # an exception for it. if ( $mimeType !== 'image/jpeg' && - $srcWidth * $srcHeight > $wgMaxImageArea ) + $this->getImageArea( $image, $srcWidth, $srcHeight ) > $wgMaxImageArea ) { return false; } @@ -39,6 +39,13 @@ class BitmapHandler extends ImageHandler { return true; } + + + // Function that returns the number of pixels to be thumbnailed. + // Intended for animated GIFs to multiply by the number of frames. + function getImageArea( $image, $width, $height ) { + return $width * $height; + } function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir; diff --git a/includes/media/GIF.php b/includes/media/GIF.php new file mode 100644 index 0000000000..b5c62eea87 --- /dev/null +++ b/includes/media/GIF.php @@ -0,0 +1,58 @@ +parsedGIFMetadata) ) + $image->parsedGIFMetadata = GIFMetadataExtractor::getMetadata( $filename ); + + return serialize($image->parsedGIFMetadata); + + } + + function formatMetadata( $image ) { + return false; + } + + function getImageArea( $image, $width, $height ) { + $metadata = unserialize($image->getMetadata()); + return $width * $height * $metadata['frameCount']; + } + + function getMetadataType( $image ) { + return 'parsed-gif'; + } + + function getLongDesc( $image ) { + global $wgUser, $wgLang; + $sk = $wgUser->getSkin(); + + $metadata = unserialize($image->getMetadata()); + + $info = array(); + $info[] = $image->getMimeType(); + $info[] = $sk->formatSize( $image->getSize() ); + + if ($metadata['looped']) + $info[] = wfMsgExt( 'file-info-gif-looped', 'parseinline' ); + + if ($metadata['frameCount'] > 1) + $info[] = wfMsgExt( 'file-info-gif-frames', 'parseinline', $metadata['frameCount'] ); + + if ($metadata['duration']) + $info[] = $wgLang->formatTimePeriod( $metadata['duration'] ); + + $infoString = $wgLang->commaList( $info ); + + return "($infoString)"; + } +} diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php new file mode 100644 index 0000000000..3f17871b29 --- /dev/null +++ b/includes/media/GIFMetadataExtractor.php @@ -0,0 +1,171 @@ + $frameCount, + 'looped' => $isLooped, + 'duration' => $duration + ); + + } + + static function readGCT( $fh, $bpp ) { + if ($bpp > 0) { + for( $i=1; $i<=pow(2,$bpp); ++$i ) { + fread( $fh, 3 ); + } + } + } + + static function decodeBPP( $data ) { + $buf = unpack( 'C', $data ); + $buf = $buf[1]; + $bpp = ( $buf & 7 ) + 1; + $buf >>= 7; + + $have_map = $buf & 1; + + return $have_map ? $bpp : 0; + } + + static function skipBlock( $fh ) { + while ( !feof( $fh ) ) { + $buf = fread( $fh, 1 ); + $block_len = unpack( 'C', $buf ); + $block_len = $block_len[1]; + if ($block_len == 0) + return; + fread( $fh, $block_len ); + } + } + +} \ No newline at end of file diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index cd97fb8658..4988c93b89 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -3411,6 +3411,8 @@ $1', 'svg-long-desc' => '(SVG file, nominally $1 × $2 pixels, file size: $3)', 'show-big-image' => 'Full resolution', 'show-big-image-thumb' => 'Size of this preview: $1 × $2 pixels', +'file-info-gif-looped' => 'looped', +'file-info-gif-frames' => '$1 frames', # Special:NewFiles 'newimages' => 'Gallery of new files', -- 2.20.1