'MediaHandler' => 'includes/media/Generic.php',
'MediaTransformError' => 'includes/media/MediaTransformOutput.php',
'MediaTransformOutput' => 'includes/media/MediaTransformOutput.php',
+ 'PNGHandler' => 'includes/media/PNG.php',
+ 'PNGMetadataExtractor' => 'includes/media/PNGMetadataExtractor.php',
'SvgHandler' => 'includes/media/SVG.php',
'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
'TiffHandler' => 'includes/media/Tiff.php',
*/
$wgMediaHandlers = array(
'image/jpeg' => 'BitmapHandler',
- 'image/png' => 'BitmapHandler',
+ 'image/png' => 'PNGHandler',
'image/gif' => 'GIFHandler',
'image/tiff' => 'TiffHandler',
'image/x-ms-bmp' => 'BmpHandler',
--- /dev/null
+<?php
+/**
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for PNG images.
+ *
+ * @ingroup Media
+ */
+class PNGHandler extends BitmapHandler {
+
+ function getMetadata( $image, $filename ) {
+ if ( !isset($image->parsedPNGMetadata) ) {
+ try {
+ $image->parsedPNGMetadata = PNGMetadataExtractor::getMetadata( $filename );
+ } catch( Exception $e ) {
+ // Broken file?
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+ return '0';
+ }
+ }
+
+ return serialize($image->parsedPNGMetadata);
+
+ }
+
+ function formatMetadata( $image ) {
+ return false;
+ }
+
+ function isAnimatedImage( $image ) {
+ $ser = $image->getMetadata();
+ if ($ser) {
+ $metadata = unserialize($ser);
+ if( $metadata['frameCount'] > 1 ) return true;
+ }
+ return false;
+ }
+
+ function getMetadataType( $image ) {
+ return 'parsed-png';
+ }
+
+ function isMetadataValid( $image, $metadata ) {
+ wfSuppressWarnings();
+ $data = unserialize( $metadata );
+ wfRestoreWarnings();
+ return (boolean) $data;
+ }
+ function getLongDesc( $image ) {
+ global $wgUser, $wgLang;
+ $sk = $wgUser->getSkin();
+ $original = parent::getLongDesc( $image );
+
+ wfSuppressWarnings();
+ $metadata = unserialize($image->getMetadata());
+ wfRestoreWarnings();
+
+ if( !metadata || $metadata['frameCount'] == 0 )
+ return $original;
+
+ $info[] = substr( $original, 1, strlen( $original )-2 );
+
+ if ($metadata['loopCount'] == 0)
+ $info[] = wfMsgExt( 'file-info-png-looped', 'parseinline' );
+ elseif ($metadata['loopCount'] > 1)
+ $info[] = wfMsgExt( 'file-info-png-repeat', 'parseinline', $metadata['loopCount'] );;
+
+ if ($metadata['frameCount'] > 0)
+ $info[] = wfMsgExt( 'file-info-png-frames', 'parseinline', $metadata['frameCount'] );
+
+ if ($metadata['duration'])
+ $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+
+ $infoString = $wgLang->commaList( $info );
+
+ return "($infoString)";
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * PNG frame counter.
+ * Based on
+ * Deliberately not using MWExceptions to avoid external dependencies, encouraging
+ * redistribution.
+ */
+
+class PNGMetadataExtractor {
+ static $png_sig;
+ static $CRC_size;
+
+ static function getMetadata( $filename ) {
+ self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
+ self::$CRC_size = 4;
+
+ $frameCount = 0;
+ $loopCount = 1;
+ $duration = 0.0;
+
+ if (!$filename)
+ throw new Exception( __METHOD__ . "No file name specified" );
+ elseif ( !file_exists($filename) || is_dir($filename) )
+ throw new Exception( __METHOD__ . "File $filename does not exist" );
+
+ $fh = fopen( $filename, 'r' );
+
+ if (!$fh)
+ throw new Exception( __METHOD__ . "Unable to open file $filename" );
+
+ // Check for the PNG header
+ $buf = fread( $fh, 8 );
+ if ( !($buf == self::$png_sig) ) {
+ throw new Exception( __METHOD__ . "Not a valid PNG file; header: $buf" );
+ }
+
+ // Read chunks
+ while( !feof( $fh ) ) {
+ $buf = fread( $fh, 4 );
+ if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; }
+ $chunk_size = unpack( "N", $buf);
+ $chunk_size = $chunk_size[1];
+
+ $chunk_type = fread( $fh, 4 );
+ if( !$chunk_type ) { throw new Exception( __METHOD__ . "Read error" ); return; }
+
+ if ( $chunk_type == "acTL" ) {
+ $buf = fread( $fh, $chunk_size );
+ if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; }
+
+ $actl = unpack( "Nframes/Nplays", $buf );
+ $frameCount = $actl['frames'];
+ $loopCount = $actl['plays'];
+ } elseif ( $chunk_type == "fcTL" ) {
+ $buf = fread( $fh, $chunk_size );
+ if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; }
+ $buf = substr( $buf, 20 );
+
+ $fctldur = unpack( "ndelay_num/ndelay_den", $buf );
+ if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100;
+ if( $fctldur['delay_num'] ) {
+ $duration += $fctldur['delay_num'] / $fctldur['delay_den'];
+ }
+ } elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) {
+ // Not a valid animated image. No point in continuing.
+ break;
+ } elseif ( $chunk_type == "IEND" ) {
+ break;
+ } else {
+ fseek( $fh, $chunk_size, SEEK_CUR );
+ }
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ }
+ fclose( $fh );
+
+ if( $loopCount > 1 ) {
+ $duration *= $loopCount;
+ }
+
+ return array(
+ 'frameCount' => $frameCount,
+ 'loopCount' => $loopCount,
+ 'duration' => $duration
+ );
+
+ }
+}
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
-image/png png
+image/png png apng
image/svg+xml svg
image/tiff tiff tif
image/vnd.djvu djvu djv
'show-big-image-thumb' => '<small>Size of this preview: $1 × $2 pixels</small>',
'file-info-gif-looped' => 'looped',
'file-info-gif-frames' => '$1 {{PLURAL:$1|frame|frames}}',
+'file-info-png-looped' => 'looped',
+'file-info-png-repeat' => 'played $1 times',
+'file-info-png-frames' => '$1 {{PLURAL:$1|frame|frames}}',
# Special:NewFiles
'newimages' => 'Gallery of new files',
'show-big-image-thumb' => 'File info displayed on file description page.',
'file-info-gif-looped' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/Gif .gif file] on its file description page. Looped means repeating in the context of an animated gif. It is a sequence of images, each displayed after the other, and the first one displayed after the last, in a never ending loop. For example of message in use see [[:File:Mouse10.gif]].',
'file-info-gif-frames' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/Gif .gif file] on its file description page.
+'file-info-png-looped' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page. Looped means repeating indefinetly in the context of an animated png. It is a sequence of images, each displayed after the other, and the first one displayed after the last, in a never ending loop.',
+'file-info-png-repeat' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page. The sequence of images is repeating a limited amount of time. It is a sequence of images, each displayed after the other, and the first one displayed after the last, for $1 times.',
+'file-info-png-frames' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page.
The variable $1 is the number of individual frames in an animated gif file.
'show-big-image-thumb',
'file-info-gif-looped',
'file-info-gif-frames',
+ 'file-info-png-looped',
+ 'file-info-png-repeat',
+ 'file-info-png-frames',
),
'newfiles' => array(
'newimages',