* Introduced media handler modules for file-type specific operations: thumbnailing...
authorTim Starling <tstarling@users.mediawiki.org>
Fri, 20 Apr 2007 12:31:36 +0000 (12:31 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Fri, 20 Apr 2007 12:31:36 +0000 (12:31 +0000)
* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally.
* Fixed interaction of page parameter to ImagePage with the HTML file cache
* Improved error reporting for image thumbnailing
* Fixed MIME type for SVG files, will be silently changed from image/svg to image/svg+xml after loading from the database.
* Workaround for djvutoxml bug #1704049 (poor performance). Use djvudump instead.
* Fixed odd behaviour in ImagePage on DjVu thumbnailing errors
* Improved error reporting for image thumbnailing
* Added sharpening option for ImageMagick thumbnailing
* Removed Image::selectPage(), added page parameters to getWidth() and getHeight(), deprecated Image::renderThumb() and Image::getThumbnail()
* Changed default contents of img_metadata to empty string instead of a:0:{}
* Moved responsibility for respecting $wgGenerateThumbnailOnParse from the UI to Image.php

23 files changed:
RELEASE-NOTES
config/index.php
includes/Article.php
includes/AutoLoader.php
includes/DefaultSettings.php
includes/DjVuImage.php
includes/Exif.php
includes/Image.php
includes/ImageGallery.php
includes/ImagePage.php
includes/Linker.php
includes/MediaTransformOutput.php [new file with mode: 0644]
includes/MimeMagic.php
includes/Parser.php
includes/media/Bitmap.php [new file with mode: 0644]
includes/media/DjVu.php [new file with mode: 0644]
includes/media/Generic.php [new file with mode: 0644]
includes/media/SVG.php [new file with mode: 0644]
includes/mime.info
languages/messages/MessagesEn.php
skins/common/common.css
skins/monobook/main.css
thumb.php

index 7d5ebc5..8c8fe15 100644 (file)
@@ -31,6 +31,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * Added rate limiter for Special:Emailuser
 * Private logs can now be created using $wgLogRestrictions
 * (Bug 8590) limited HTML is now always enabled ($wgUserHtml = true).
+* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally.
 
 == New features since 1.9 ==
 
@@ -117,6 +118,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * Introduce 'SearchUpdate' hook; see docs/hooks.txt for more information
 * Introduce 'mywatchlist' message; used on personal menu to link to watchlist page
 * Introduce magic word {{NUMBEROFEDITS}}
+* Introduced media handlers for file-type specific operations. 
 
 == Bugfixes since 1.9 ==
 
@@ -327,6 +329,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
   a random page, and will give an error message if none really can be found
   instead of sending the user to the main page like they used to
 * Fix object variable used for displaying "not-patrolled" CSS class on list
+* Fixed interaction of page parameter to ImagePage with the HTML file cache
+* 
 
 == Maintenance ==
 
index 4b987cd..b392a6d 100644 (file)
@@ -483,8 +483,6 @@ if( $conf->HaveGD ) {
        }
 }
 
-$conf->UseImageResize = $conf->HaveGD || $conf->ImageMagick;
-
 $conf->IP = dirname( dirname( __FILE__ ) );
 print "<li>Installation directory: <tt>" . htmlspecialchars( $conf->IP ) . "</tt></li>\n";
 
@@ -1302,7 +1300,6 @@ function escapePhpString( $string ) {
 }
 
 function writeLocalSettings( $conf ) {
-       $conf->UseImageResize = $conf->UseImageResize ? 'true' : 'false';
        $conf->PasswordSender = $conf->EmergencyContact;
        $magic = ($conf->ImageMagick ? "" : "# ");
        $convert = ($conf->ImageMagick ? $conf->ImageMagick : "/usr/bin/convert" );
@@ -1448,7 +1445,6 @@ if ( \$wgCommandLineMode ) {
 ## To enable image uploads, make sure the 'images' directory
 ## is writable, then set this to true:
 \$wgEnableUploads       = false;
-\$wgUseImageResize      = {$conf->UseImageResize};
 {$magic}\$wgUseImageMagick = true;
 {$magic}\$wgImageMagickConvertCommand = \"{$convert}\";
 
index 1fb42b3..c48e2ed 100644 (file)
@@ -2460,6 +2460,7 @@ class Article {
                $diff      = $wgRequest->getVal( 'diff'      );
                $redirect  = $wgRequest->getVal( 'redirect'  );
                $printable = $wgRequest->getVal( 'printable' );
+               $page      = $wgRequest->getVal( 'page' );
 
                return $wgUseFileCache
                        and (!$wgShowIPinHeader)
@@ -2472,6 +2473,7 @@ class Article {
                        and (!isset($diff))
                        and (!isset($redirect))
                        and (!isset($printable))
+                       and !isset($page)
                        and (!$this->mRedirectedFrom);
        }
 
index 5d334da..23d0384 100644 (file)
@@ -8,6 +8,7 @@ function __autoload($className) {
        global $wgAutoloadClasses;
 
        static $localClasses = array(
+               # Includes
                'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
                'AjaxCachePolicy' => 'includes/AjaxFunctions.php',
                'AjaxResponse' => 'includes/AjaxResponse.php',
@@ -115,6 +116,10 @@ function __autoload($className) {
                'MacBinary' => 'includes/MacBinary.php',
                'MagicWord' => 'includes/MagicWord.php',
                'MathRenderer' => 'includes/Math.php',
+               'MediaTransformOutput' => 'includes/MediaTransformOutput.php',
+               'ThumbnailImage' => 'includes/MediaTransformOutput.php',
+               'MediaTransformError' => 'includes/MediaTransformOutput.php',
+               'TransformParameterError' => 'includes/MediaTransformOutput.php',
                'MessageCache' => 'includes/MessageCache.php',
                'MimeMagic' => 'includes/MimeMagic.php',
                'Namespace' => 'includes/Namespace.php',
@@ -128,6 +133,7 @@ function __autoload($className) {
                'ParserOutput' => 'includes/ParserOutput.php',
                'ParserOptions' => 'includes/ParserOptions.php',
                'ParserCache' => 'includes/ParserCache.php',
+               'PatrolLog' => 'includes/PatrolLog.php',
                'ProfilerSimple' => 'includes/ProfilerSimple.php',
                'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php',
                'Profiler' => 'includes/Profiler.php',
@@ -196,6 +202,7 @@ function __autoload($className) {
                'PopularPagesPage' => 'includes/SpecialPopularpages.php',
                'PreferencesForm' => 'includes/SpecialPreferences.php',
                'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php',
+               'PasswordResetForm' => 'includes/SpecialResetpass.php',
                'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php',
                'RevisionDeleter' => 'includes/SpecialRevisiondelete.php',
                'SpecialSearch' => 'includes/SpecialSearch.php',
@@ -240,15 +247,26 @@ function __autoload($className) {
                'Xml' => 'includes/Xml.php',
                'ZhClient' => 'includes/ZhClient.php',
                'memcached' => 'includes/memcached-client.php',
+
+               # Media
+               'BitmapHandler' => 'includes/media/Bitmap.php',
+               'DjVuHandler' => 'includes/media/DjVu.php',
+               'MediaHandler' => 'includes/media/Generic.php',
+               'ImageHandler' => 'includes/media/Generic.php',
+               'SvgHandler' => 'includes/media/SVG.php',
+
+               # Normal
                'UtfNormal' => 'includes/normal/UtfNormal.php',
+
+               # Templates
                'UsercreateTemplate' => 'includes/templates/Userlogin.php',
                'UserloginTemplate' => 'includes/templates/Userlogin.php',
+
+               # Languages
                'Language' => 'languages/Language.php',
-               'PasswordResetForm' => 'includes/SpecialResetpass.php',
-               'PatrolLog' => 'includes/PatrolLog.php',
                'RandomPage' => 'includes/SpecialRandompage.php',
 
-               // API classes
+               # API
                'ApiBase' => 'includes/api/ApiBase.php',
                'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
                'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
index cd49b0a..54a2686 100644 (file)
@@ -1443,8 +1443,18 @@ $wgSiteNotice = '';
 # Images settings
 #
 
-/** dynamic server side image resizing ("Thumbnails") */
-$wgUseImageResize              = false;
+/** 
+ * Plugins for media file type handling.
+ * Each entry in the array maps a MIME type to a class name
+ */
+$wgMediaHandlers = array(
+       'image/jpeg' => 'BitmapHandler',
+       'image/png' => 'BitmapHandler',
+       'image/gif' => 'BitmapHandler',
+       'image/svg+xml' => 'SvgHandler',
+       'image/vnd.djvu' => 'DjVuHandler',
+);
+
 
 /**
  * Resizing can be done using PHP's internal image libraries or using
@@ -1458,6 +1468,12 @@ $wgUseImageMagick                = false;
 /** The convert command shipped with ImageMagick */
 $wgImageMagickConvertCommand    = '/usr/bin/convert';
 
+/** Sharpening parameter to ImageMagick */
+$wgSharpenParameter = '0x0.4';
+
+/** Reduction in linear dimensions below which sharpening will be enabled */
+$wgSharpenReductionThreshold = 0.85;
+
 /**
  * Use another resizing converter, e.g. GraphicMagick
  * %s will be replaced with the source path, %d with the destination
@@ -1523,6 +1539,10 @@ $wgIgnoreImageErrors = false;
  */
 $wgGenerateThumbnailOnParse = true;
 
+/** Obsolete, always true, kept for compatibility with extensions */
+$wgUseImageResize              = true;
+
+
 /** Set $wgCommandLineMode if it's not set already, to avoid notices */
 if( !isset( $wgCommandLineMode ) ) {
        $wgCommandLineMode = false;
@@ -2277,7 +2297,7 @@ $wgTrustedMediaFormats= array(
        MEDIATYPE_BITMAP, //all bitmap formats
        MEDIATYPE_AUDIO,  //all audio formats
        MEDIATYPE_VIDEO,  //all plain video formats
-       "image/svg",  //svg (only needed if inline rendering of svg is not supported)
+       "image/svg+xml",  //svg (only needed if inline rendering of svg is not supported)
        "application/pdf",  //PDF files
        #"application/x-shockwave-flash", //flash/shockwave movie
 );
@@ -2380,7 +2400,7 @@ $wgReservedUsernames = array(
  * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
  * perform basic stuff like MIME detection and which are vulnerable to further idiots uploading
  * crap files as images. When this directive is on, <title> will be allowed in files with
- * an "image/svg" MIME type. You should leave this disabled if your web server is misconfigured
+ * an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured
  * and doesn't send appropriate MIME types for SVG images.
  */
 $wgAllowTitlesInSVG = false;
@@ -2406,19 +2426,32 @@ $wgMaxShellFileSize = 102400;
 
 /**
  * DJVU settings
- * Path of the djvutoxml executable
+ * Path of the djvudump executable
  * Enable this and $wgDjvuRenderer to enable djvu rendering
  */
-# $wgDjvuToXML = 'djvutoxml';
-$wgDjvuToXML = null;
+# $wgDjvuDump = 'djvudump';
+$wgDjvuDump = null;
 
 /**
  * Path of the ddjvu DJVU renderer
- * Enable this and $wgDjvuToXML to enable djvu rendering
+ * Enable this and $wgDjvuDump to enable djvu rendering
  */
 # $wgDjvuRenderer = 'ddjvu';
 $wgDjvuRenderer = null;
 
+/**
+ * Path of the djvutoxml executable
+ * This works like djvudump except much, much slower as of version 3.5. 
+ *
+ * For now I recommend you use djvudump instead. The djvuxml output is
+ * probably more stable, so we'll switch back to it as soon as they fix
+ * the efficiency problem.
+ * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
+ */
+# $wgDjvuToXML = 'djvutoxml';
+$wgDjvuToXML = null;
+
+
 /**
  * Shell command for the DJVU post processor
  * Default: pnmtopng, since ddjvu generates ppm output
index c63791c..cd85147 100644 (file)
@@ -220,17 +220,121 @@ class DjVuImage {
         * @return string
         */
        function retrieveMetaData() {
-               global $wgDjvuToXML;
-               if ( isset( $wgDjvuToXML ) ) {
+               global $wgDjvuToXML, $wgDjvuDump;
+               if ( isset( $wgDjvuDump ) ) {
+                       # djvudump is faster as of version 3.5
+                       # http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
+                       wfProfileIn( 'djvudump' );
+                       $cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename );
+                       $dump = wfShellExec( $cmd );
+                       $xml = $this->convertDumpToXML( $dump );
+                       wfProfileOut( 'djvudump' );
+               } elseif ( isset( $wgDjvuToXML ) ) {
+                       wfProfileIn( 'djvutoxml' );
                        $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' .
                                wfEscapeShellArg( $this->mFilename );
                        $xml = wfShellExec( $cmd );
+                       wfProfileOut( 'djvutoxml' );
                } else {
                        $xml = null;
                }
                return $xml;
        }
-               
+
+       /**
+        * Hack to temporarily work around djvutoxml bug
+        */
+       function convertDumpToXML( $dump ) {
+               if ( strval( $dump ) == '' ) {
+                       return false;
+               }
+
+               $xml = <<<EOT
+<?xml version="1.0" ?>
+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
+<DjVuXML>
+<HEAD></HEAD>
+<BODY>
+EOT;
+
+               $dump = str_replace( "\r", '', $dump );
+               $line = strtok( $dump, "\n" );
+               $m = false;
+               $good = false;
+               if ( preg_match( '/^( *)FORM:DJVU/', $line, $m ) ) {
+                       # Single-page
+                       if ( $this->parseFormDjvu( $line, $xml ) ) {
+                               $good = true;
+                       } else {
+                               return false;
+                       }
+               } elseif ( preg_match( '/^( *)FORM:DJVM/', $line, $m ) ) {
+                       # Multi-page
+                       $parentLevel = strlen( $m[1] );
+                       # Find DIRM
+                       $line = strtok( "\n" );
+                       while ( $line !== false ) {
+                               $childLevel = strspn( $line, ' ' );
+                               if ( $childLevel <= $parentLevel ) {
+                                       # End of chunk
+                                       break;
+                               }
+
+                               if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
+                                       wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
+                                       return false;
+                               }
+                               if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
+                                       # Found page
+                                       if ( $this->parseFormDjvu( $line, $xml ) ) {
+                                               $good = true;
+                                       } else {
+                                               return false;
+                                       }
+                               }
+                               $line = strtok( "\n" );
+                       }
+               }
+               if ( !$good ) {
+                       return false;
+               }
+
+               $xml .= "</BODY>\n</DjVuXML>\n";
+               return $xml;
+       }
+
+       function parseFormDjvu( $line, &$xml ) {
+               $parentLevel = strspn( $line, ' ' );
+               $line = strtok( "\n" );
+
+               # Find INFO
+               while ( $line !== false ) {
+                       $childLevel = strspn( $line, ' ' );
+                       if ( $childLevel <= $parentLevel ) {
+                               # End of chunk
+                               break;
+                       }
+
+                       if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) {
+                               $xml .= Xml::tags( 'OBJECT', 
+                                       array(
+                                               #'data' => '',
+                                               #'type' => 'image/x.djvu',
+                                               'height' => $m[2],
+                                               'width' => $m[1],
+                                               #'usemap' => '',
+                                       ), 
+                                       "\n" .
+                                       Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
+                                       Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
+                               ) . "\n";
+                               return true;
+                       }
+                       $line = strtok( "\n" );
+               }
+               # Not found
+               return false;
+       }
 }
 
 
index a368908..f9de28a 100644 (file)
@@ -93,9 +93,9 @@ class Exif {
        var $basename;
 
        /**
-        * The private log to log to
+        * The private log to log to, e.g. 'exif'
         */
-       var $log = 'exif';
+       var $log = false;
 
        //@}
 
@@ -561,7 +561,10 @@ class Exif {
         * @param $fname String: 
         * @param $action Mixed: , default NULL.
         */
-        function debug( $in, $fname, $action = NULL ) {
+       function debug( $in, $fname, $action = NULL ) {
+               if ( !$this->log ) {
+                       return;
+               }
                $type = gettype( $in );
                $class = ucfirst( __CLASS__ );
                if ( $type === 'array' )
@@ -586,6 +589,9 @@ class Exif {
         * @param $io Boolean: Specify whether we're beginning or ending
         */
        function debugFile( $fname, $io ) {
+               if ( !$this->log ) {
+                       return;
+               }
                $class = ucfirst( __CLASS__ );
                if ( $io ) {
                        wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
index 6a5e730..c6b23ac 100644 (file)
@@ -27,7 +27,8 @@ class Image
        const DELETED_FILE = 1;
        const DELETED_COMMENT = 2;
        const DELETED_USER = 4;
-    const DELETED_RESTRICTED = 8;
+       const DELETED_RESTRICTED = 8;
+       const RENDER_NOW = 1;
     
        /**#@+
         * @private
@@ -85,13 +86,12 @@ class Image
                }
                $this->title =& $title;
                $this->name = $title->getDBkey();
-               $this->metadata = serialize ( array() ) ;
+               $this->metadata = '';
 
                $n = strrpos( $this->name, '.' );
                $this->extension = Image::normalizeExtension( $n ?
                        substr( $this->name, $n + 1 ) : '' );
                $this->historyLine = 0;
-               $this->page = 1;
 
                $this->dataLoaded = false;
        }
@@ -244,7 +244,6 @@ class Image
                $this->fileExists = file_exists( $this->imagePath );
                $this->fromSharedDirectory = false;
                $gis = array();
-               $deja = false;
 
                if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n");
 
@@ -268,18 +267,26 @@ class Image
 
                        $this->mime = $magic->guessMimeType($this->imagePath,true);
                        $this->type = $magic->getMediaType($this->imagePath,$this->mime);
+                       $handler = MediaHandler::getHandler( $this->mime );
 
                        # Get size in bytes
                        $this->size = filesize( $this->imagePath );
 
-                       # Height and width
-                       $gis = self::getImageSize( $this->imagePath, $this->mime, $deja );
+                       # Height, width and metadata
+                       if ( $handler ) {
+                               $gis = $handler->getImageSize( $this, $this->imagePath );
+                               $this->metadata = $handler->getMetadata( $this, $this->imagePath );
+                       } else {
+                               $gis = false;
+                               $this->metadata = '';
+                       }
 
                        wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
                }
                else {
                        $this->mime = NULL;
                        $this->type = MEDIATYPE_UNKNOWN;
+                       $this->metadata = '';
                        wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
                }
 
@@ -298,13 +305,6 @@ class Image
                # as ther's only one thread of execution, this should be safe anyway.
                $this->dataLoaded = true;
 
-
-               if ( $deja ) {
-                       $this->metadata = $deja->retrieveMetaData();
-               } else {
-                       $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
-               }
-
                if ( isset( $gis['bits'] ) )  $this->bits = $gis['bits'];
                else $this->bits = 0;
 
@@ -331,9 +331,7 @@ class Image
                        $this->loadFromRow( $row );
                        $this->imagePath = $this->getFullPath();
                        // Check for rows from a previous schema, quietly upgrade them
-                       if ( is_null($this->type) ) {
-                               $this->upgradeRow();
-                       }
+                       $this->maybeUpgradeRow();
                } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
                        # In case we're on a wgCapitalLinks=false wiki, we
                        # capitalize the first letter of the filename before
@@ -354,9 +352,7 @@ class Image
                                $this->loadFromRow( $row );
 
                                // Check for rows from a previous schema, quietly upgrade them
-                               if ( is_null($this->type) ) {
-                                       $this->upgradeRow();
-                               }
+                               $this->maybeUpgradeRow();
                        }
                }
 
@@ -368,7 +364,7 @@ class Image
                        $this->type = 0;
                        $this->fileExists = false;
                        $this->fromSharedDirectory = false;
-                       $this->metadata = serialize ( array() ) ;
+                       $this->metadata = '';
                        $this->mime = false;
                }
 
@@ -395,9 +391,7 @@ class Image
                        if (!$minor) $minor= "unknown";
                        $this->mime = $major.'/'.$minor;
                }
-
                $this->metadata = $row->img_metadata;
-               if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ;
 
                $this->dataLoaded = true;
        }
@@ -424,8 +418,21 @@ class Image
        }
 
        /**
-        * Metadata was loaded from the database, but the row had a marker indicating it needs to be
-        * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row.
+        * Upgrade a row if it needs it
+        */
+       function maybeUpgradeRow() {
+               if ( is_null($this->type) || $this->mime == 'image/svg' ) {
+                       $this->upgradeRow();
+               } else {
+                       $handler = $this->getHandler();
+                       if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
+                               $this->upgradeRow();
+                       }
+               }
+       }
+
+       /**
+        * Fix assorted version-related problems with the image row by reloading it from the file
         */
        function upgradeRow() {
                global $wgDBname, $wgSharedUploadDBname;
@@ -450,7 +457,7 @@ class Image
 
                list( $major, $minor ) = self::splitMime( $this->mime );
 
-               wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n");
+               wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
 
                $dbw->update( 'image',
                        array(
@@ -543,23 +550,49 @@ class Image
        /**
         * Return the width of the image
         *
-        * Returns -1 if the file specified is not a known image type
+        * Returns false on error
         * @public
         */
-       function getWidth() {
+       function getWidth( $page = 1 ) {
                $this->load();
-               return $this->width;
+               if ( $this->isMultipage() ) {
+                       $dim = $this->getHandler()->getPageDimensions( $this, $page );
+                       if ( $dim ) {
+                               return $dim['width'];
+                       } else {
+                               return false;
+                       }
+               } else {
+                       return $this->width;
+               }
        }
 
        /**
         * Return the height of the image
         *
-        * Returns -1 if the file specified is not a known image type
+        * Returns false on error
         * @public
         */
-       function getHeight() {
+       function getHeight( $page = 1 ) {
+               $this->load();
+               if ( $this->isMultipage() ) {
+                       $dim = $this->getHandler()->getPageDimensions( $this, $page );
+                       if ( $dim ) {
+                               return $dim['height'];
+                       } else {
+                               return false;
+                       }
+               } else {
+                       return $this->height;
+               }
+       }
+
+       /**
+        * Get handler-specific metadata
+        */
+       function getMetadata() {
                $this->load();
-               return $this->height;
+               return $this->metadata;
        }
 
        /**
@@ -599,58 +632,10 @@ class Image
         * @todo remember the result of this check.
         */
        function canRender() {
-               global $wgUseImageMagick, $wgDjvuRenderer;
-
-               if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
-
-               $mime= $this->getMimeType();
-
-               if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false;
-
-               #if it's SVG, check if there's a converter enabled
-               if ($mime === 'image/svg' || $mime == 'image/svg+xml' ) {
-                       global $wgSVGConverters, $wgSVGConverter;
-
-                       if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) {
-                               wfDebug( "Image::canRender: SVG is ready!\n" );
-                               return true;
-                       } else {
-                               wfDebug( "Image::canRender: SVG renderer missing\n" );
-                       }
-               }
-
-               #image formats available on ALL browsers
-               if (  $mime === 'image/gif'
-                  || $mime === 'image/png'
-                  || $mime === 'image/jpeg' ) return true;
-
-               #image formats that can be converted to the above formats
-               if ($wgUseImageMagick) {
-                       #convertable by ImageMagick (there are more...)
-                       if ( $mime === 'image/vnd.wap.wbmp'
-                         || $mime === 'image/x-xbitmap'
-                         || $mime === 'image/x-xpixmap'
-                         #|| $mime === 'image/x-icon'   #file may be split into multiple parts
-                         || $mime === 'image/x-portable-anymap'
-                         || $mime === 'image/x-portable-bitmap'
-                         || $mime === 'image/x-portable-graymap'
-                         || $mime === 'image/x-portable-pixmap'
-                         #|| $mime === 'image/x-photoshop'  #this takes a lot of CPU and RAM!
-                         || $mime === 'image/x-rgb'
-                         || $mime === 'image/x-bmp'
-                         || $mime === 'image/tiff' ) return true;
-               }
-               else {
-                       #convertable by the PHP GD image lib
-                       if ( $mime === 'image/vnd.wap.wbmp'
-                         || $mime === 'image/x-xbitmap' ) return true;
-               }
-               if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true;
-
-               return false;
+               $handler = $this->getHandler();
+               return $handler && $handler->canRender();
        }
 
-
        /**
         * Return true if the file is of a type that can't be directly
         * rendered by typical browsers and needs to be re-rasterized.
@@ -662,13 +647,8 @@ class Image
         * @return bool
         */
        function mustRender() {
-               $mime= $this->getMimeType();
-
-               if (  $mime === "image/gif"
-                  || $mime === "image/png"
-                  || $mime === "image/jpeg" ) return false;
-
-               return true;
+               $handler = $this->getHandler();
+               return $handler && $handler->mustRender();
        }
 
        /**
@@ -734,15 +714,7 @@ class Image
         * @public
         */
        function getEscapeLocalURL( $query=false) {
-               $this->getTitle();
-               if ( $query === false ) {
-                       if ( $this->page != 1 ) {
-                               $query = 'page=' . $this->page;
-                       } else {
-                               $query = '';
-                       }
-               }
-               return $this->title->escapeLocalURL( $query );
+               return $this->getTitle()->escapeLocalURL( $query );
        }
 
        /**
@@ -790,77 +762,72 @@ class Image
         * @todo document
         * @private
         */
-       function thumbUrl( $width, $subdir='thumb') {
+       function thumbUrl( $thumbName ) {
                global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath;
-               global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
-
-               // Generate thumb.php URL if possible
-               $script = false;
-               $url = false;
+               if($this->fromSharedDirectory) {
+                       $base = '';
+                       $path = $wgSharedUploadPath;
+               } else {
+                       $base = $wgUploadBaseUrl;
+                       $path = $wgUploadPath;
+               }
+               if ( Image::isHashed( $this->fromSharedDirectory ) ) {
+                       $url = "{$base}{$path}/thumb" .
+                       wfGetHashPath($this->name, $this->fromSharedDirectory)
+                       . $this->name.'/'.$thumbName;
+                       $url = wfUrlencode( $url );
+               } else {
+                       $url = "{$base}{$path}/thumb/{$thumbName}";
+               }
+               return $url;
+       }
 
+       function getTransformScript() {
+               global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
                if ( $this->fromSharedDirectory ) {
-                       if ( $wgSharedThumbnailScriptPath ) {
-                               $script = $wgSharedThumbnailScriptPath;
-                       }
+                       $script = $wgSharedThumbnailScriptPath;
                } else {
-                       if ( $wgThumbnailScriptPath ) {
-                               $script = $wgThumbnailScriptPath;
-                       }
+                       $script = $wgThumbnailScriptPath;
                }
                if ( $script ) {
-                       $url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
-                       if( $this->mustRender() ) {
-                               $url.= '&r=1';
-                       }
+                       return "$script?f=" . urlencode( $this->name );
                } else {
-                       $name = $this->thumbName( $width );
-                       if($this->fromSharedDirectory) {
-                               $base = '';
-                               $path = $wgSharedUploadPath;
-                       } else {
-                               $base = $wgUploadBaseUrl;
-                               $path = $wgUploadPath;
-                       }
-                       if ( Image::isHashed( $this->fromSharedDirectory ) ) {
-                               $url = "{$base}{$path}/{$subdir}" .
-                               wfGetHashPath($this->name, $this->fromSharedDirectory)
-                               . $this->name.'/'.$name;
-                               $url = wfUrlencode( $url );
-                       } else {
-                               $url = "{$base}{$path}/{$subdir}/{$name}";
-                       }
+                       return false;
                }
-               return array( $script !== false, $url );
        }
 
        /**
-        * Return the file name of a thumbnail of the specified width
+        * Get a ThumbnailImage which is the same size as the source
+        */
+       function getUnscaledThumb( $page = false ) {
+               if ( $page ) {
+                       $params = array(
+                               'page' => $page,
+                               'width' => $this->getWidth( $page )
+                       );
+               } else {
+                       $params = array( 'width' => $this->getWidth() );
+               }
+               return $this->transform( $params );
+       }
+
+       /**
+        * Return the file name of a thumbnail with the specified parameters
         *
-        * @param integer $width        Width of the thumbnail image
-        * @param boolean $shared       Does the thumbnail come from the shared repository?
+        * @param array $params Handler-specific parameters
         * @private
         */
-       function thumbName( $width ) {
-               global $wgDjvuOutputExtension;
-               $thumb = $width."px-".$this->name;
-               if ( $this->page != 1 ) {
-                       $thumb = "page{$this->page}-$thumb";
+       function thumbName( $params ) {
+               $handler = $this->getHandler();
+               if ( !$handler ) {
+                       return null;
                }
-
-               if( $this->mustRender() ) {
-                       if( $this->canRender() ) {
-                               list( $ext, $mime ) = self::getThumbType( $this->extension, $this->mime );
-                               if ( $ext != $this->extension ) {
-                                       $thumb .= ".$ext";
-                               }
-                       }
-                       else {
-                               #should we use iconThumb here to get a symbolic thumbnail?
-                               #or should we fail with an internal error?
-                               return NULL; //can't make bitmap
-                       }
+               list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
+               $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
+               if ( $thumbExt != $this->extension ) {
+                       $thumbName .= ".$thumbExt";
                }
-               return $thumb;
+               return $thumbName;
        }
 
        /**
@@ -879,9 +846,13 @@ class Image
         * @param integer $height       maximum height of the image (optional)
         * @public
         */
-       function createThumb( $width, $height=-1 ) {
-               $thumb = $this->getThumbnail( $width, $height );
-               if( is_null( $thumb ) ) return '';
+       function createThumb( $width, $height = -1 ) {
+               $params = array( 'width' => $width );
+               if ( $height != -1 ) {
+                       $params['height'] = $height;
+               }
+               $thumb = $this->transform( $params );
+               if( is_null( $thumb ) || $thumb->isError() ) return '';
                return $thumb->getUrl();
        }
 
@@ -899,149 +870,89 @@ class Image
         *
         * @return ThumbnailImage or null on failure
         * @public
+        *
+        * @deprecated use transform()
         */
        function getThumbnail( $width, $height=-1, $render = true ) {
-               wfProfileIn( __METHOD__ );
-               if ($this->canRender()) {
-                       if ( $height > 0 ) {
-                               $this->load();
-                               if ( $width > $this->width * $height / $this->height ) {
-                                       $width = wfFitBoxWidth( $this->width, $this->height, $height );
-                               }
-                       }
-                       if ( $render ) {
-                               $thumb = $this->renderThumb( $width );
-                       } else {
-                               // Don't render, just return the URL
-                               if ( $this->validateThumbParams( $width, $height ) ) {
-                                       if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
-                                               $url = $this->getURL();
-                                       } else {
-                                               list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width );
-                                       }
-                                       $thumb = new ThumbnailImage( $url, $width, $height );
-                               } else {
-                                       $thumb = null;
-                               }
-                       }
-               } else {
-                       // not a bitmap or renderable image, don't try.
-                       $thumb = $this->iconThumb();
+               $params = array( 'width' => $width );
+               if ( $height != -1 ) {
+                       $params['height'] = $height;
                }
-               wfProfileOut( __METHOD__ );
-               return $thumb;
+               $flags = $render ? self::RENDER_NOW : 0;
+               return $this->transform( $params, $flags );
        }
-
-       /**
-        * @return ThumbnailImage
-        */
-       function iconThumb() {
-               global $wgStylePath, $wgStyleDirectory;
-
-               $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
-               foreach( $try as $icon ) {
-                       $path = '/common/images/icons/' . $icon;
-                       $filepath = $wgStyleDirectory . $path;
-                       if( file_exists( $filepath ) ) {
-                               return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
-                       }
-               }
-               return null;
-       }
-
+       
        /**
-        * Validate thumbnail parameters and fill in the correct height
+        * Transform a media file
         *
-        * @param integer &$width Specified width (input/output)
-        * @param integer &$height Height (output only)
-        * @return false to indicate that an error should be returned to the user. 
+        * @param array $params An associative array of handler-specific parameters. Typical 
+        *                      keys are width, height and page.
+        * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
+        * @return MediaTransformOutput
         */
-       function validateThumbParams( &$width, &$height ) {
-               global $wgSVGMaxSize, $wgMaxImageArea;
+       function transform( $params, $flags = 0 ) {
+               global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
 
-               $this->load();
+               wfProfileIn( __METHOD__ );
+               do {
+                       $handler = $this->getHandler();
+                       if ( !$handler || !$handler->canRender() ) {
+                               // not a bitmap or renderable image, don't try.
+                               $thumb = $this->iconThumb();
+                               break;
+                       }
 
-               if ( ! $this->exists() )
-               {
-                       # If there is no image, there will be no thumbnail
-                       return false;
-               }
+                       $script = $this->getTransformScript();
+                       if ( $script && !($flags & self::RENDER_NOW) ) {
+                               // Use a script to transform on client request
+                               $thumb = $handler->getScriptedTransform( $this, $script, $params );
+                               break;
+                       }
 
-               $width = intval( $width );
+                       $handler->normaliseParams( $this, $params );
+                       list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
+                       $thumbName = $this->thumbName( $params );       
+                       $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) .  "/$thumbName";
+                       $thumbUrl = $this->thumbUrl( $thumbName );
 
-               # Sanity check $width
-               if( $width <= 0 || $this->width <= 0) {
-                       # BZZZT
-                       return false;
-               }
+                       $this->migrateThumbFile( $thumbName );
 
-               # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
-               # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
-               # an exception for it.
-               if ( $this->getMediaType() == MEDIATYPE_BITMAP &&
-                       $this->getMimeType() !== 'image/jpeg' &&
-                       $this->width * $this->height > $wgMaxImageArea )
-               {
-                       return false;
-               }
+                       if ( file_exists( $thumbPath ) ) {
+                               $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+                               break;
+                       }
 
-               # Don't make an image bigger than the source, or wgMaxSVGSize for SVGs
-               if ( $this->mustRender() ) {
-                       $width = min( $width, $wgSVGMaxSize );
-               } elseif ( $width > $this->width - 1 ) {
-                       $width = $this->width;
-                       $height = $this->height;
-                       return true;
-               }
+                       if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
+                               $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+                               break;
+                       }
+                       $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
+
+                       // Ignore errors if requested
+                       if ( !$thumb ) {
+                               $thumb = null;
+                       } elseif ( $thumb->isError() ) {
+                               $this->lastError = $thumb->toText();
+                               if ( $wgIgnoreImageErrors ) {
+                                       $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+                               }
+                       }
+                       
+                       if ( $wgUseSquid ) {
+                               wfPurgeSquidServers( array( $thumbUrl ) );
+                       }
+               } while (false);
 
-               $height = self::scaleHeight( $this->width, $this->height, $width );
-               return true;
+               wfProfileOut( __METHOD__ );
+               return $thumb;
        }
 
        /**
-        * Create a thumbnail of the image having the specified width.
-        * The thumbnail will not be created if the width is larger than the
-        * image's width. Let the browser do the scaling in this case.
-        * The thumbnail is stored on disk and is only computed if the thumbnail
-        * file does not exist OR if it is older than the image.
-        * Returns an object which can return the pathname, URL, and physical
-        * pixel size of the thumbnail -- or null on failure.
-        *
-        * @return ThumbnailImage or null on failure
-        * @private
+        * Fix thumbnail files from 1.4 or before, with extreme prejudice
         */
-       function renderThumb( $width, $useScript = true ) {
-               global $wgUseSquid, $wgThumbnailEpoch;
-
-               wfProfileIn( __METHOD__ );
-
-               $this->load();
-               $height = -1;
-               if ( !$this->validateThumbParams( $width, $height ) ) {
-                       # Validation error
-                       wfProfileOut( __METHOD__ );
-                       return null;
-               }
-
-               if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
-                       # validateThumbParams (or the user) wants us to return the unscaled image
-                       $thumb = new ThumbnailImage( $this->getURL(), $width, $height );
-                       wfProfileOut( __METHOD__ );
-                       return $thumb;
-               }
-
-               list( $isScriptUrl, $url ) = $this->thumbUrl( $width );
-               if ( $isScriptUrl && $useScript ) {
-                       // Use thumb.php to render the image
-                       $thumb = new ThumbnailImage( $url, $width, $height );
-                       wfProfileOut( __METHOD__ );
-                       return $thumb;
-               }
-
-               $thumbName = $this->thumbName( $width, $this->fromSharedDirectory );
+       function migrateThumbFile( $thumbName ) {
                $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
-               $thumbPath = $thumbDir.'/'.$thumbName;
-
+               $thumbPath = "$thumbDir/$thumbName";
                if ( is_dir( $thumbPath ) ) {
                        // Directory where file should be
                        // This happened occasionally due to broken migration code in 1.5
@@ -1054,247 +965,50 @@ class Image
                                        break;
                                }
                        }
-                       // Code below will ask if it exists, and the answer is now no
+                       // Doesn't exist anymore
                        clearstatcache();
                }
-
-               $done = true;
-               if ( !file_exists( $thumbPath ) ||
-                       filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) ) 
-               {
-                       // Create the directory if it doesn't exist
-                       if ( is_file( $thumbDir ) ) {
-                               // File where thumb directory should be, destroy if possible
-                               @unlink( $thumbDir );
-                       }
-                       wfMkdirParents( $thumbDir );
-
-                       $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).
-                               '/'.$thumbName;
-                       $done = false;
-
-                       // Migration from old directory structure
-                       if ( is_file( $oldThumbPath ) ) {
-                               if ( filemtime($oldThumbPath) >= filemtime($this->imagePath) ) {
-                                       if ( file_exists( $thumbPath ) ) {
-                                               if ( !is_dir( $thumbPath ) ) {
-                                                       // Old image in the way of rename
-                                                       unlink( $thumbPath );
-                                               } else {
-                                                       // This should have been dealt with already
-                                                       throw new MWException( "Directory where image should be: $thumbPath" );
-                                               }
-                                       }
-                                       // Rename the old image into the new location
-                                       rename( $oldThumbPath, $thumbPath );
-                                       $done = true;
-                               } else {
-                                       unlink( $oldThumbPath );
-                               }
-                       }
-                       if ( !$done ) {
-                               $this->lastError = self::reallyRenderThumb( $this->imagePath, $thumbPath, $this->mime, 
-                                       $width, $height, $this->page );
-                               if ( $this->lastError === true ) {
-                                       $done = true;
-                               } elseif( $GLOBALS['wgIgnoreImageErrors'] ) {
-                                       // Log the error but output anyway.
-                                       // With luck it's a transitory error...
-                                       $done = true;
-                               }
-
-                               # Purge squid
-                               # This has to be done after the image is updated and present for all machines on NFS,
-                               # or else the old version might be stored into the squid again
-                               if ( $wgUseSquid ) {
-                                       $urlArr = array( $url );
-                                       wfPurgeSquidServers($urlArr);
-                               }
-                       }
-               }
-
-               if ( $done ) {
-                       $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
-               } else {
-                       $thumb = null;
+               if ( is_file( $thumbDir ) ) {
+                       // File where directory should be
+                       unlink( $thumbDir );
+                       // Doesn't exist anymore
+                       clearstatcache();
                }
-               wfProfileOut( __METHOD__ );
-               return $thumb;
-       } // END OF function renderThumb
-
-       /**
-        * Really render a thumbnail
-        * Call this only for images for which canRender() returns true.
-        * 
-        * @param string $source Source filename
-        * @param string $destination Destination filename
-        * @param string $mime MIME type of source
-        * @param integer $width Destination width in pixels
-        * @param integer $height Destination height in pixels
-        * @param integer $page Which page of a multi-page document to display. Ignored 
-        *           for source MIME types which do not support multiple pages.
-        */
-       static function reallyRenderThumb( $source, $destination, $mime, $width, $height, $page = false ) {
-               global $wgSVGConverters, $wgSVGConverter;
-               global $wgUseImageMagick, $wgImageMagickConvertCommand;
-               global $wgCustomConvertCommand;
-               global $wgDjvuRenderer, $wgDjvuPostProcessor;
-
-               $err = false;
-               $cmd = "";
-               $retval = 0;
-
-               if( $mime == "image/svg" || $mime == 'image/svg+xml' ) {
-                       #Right now we have only SVG
-
-                       global $wgSVGConverters, $wgSVGConverter;
-                       if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
-                               global $wgSVGConverterPath;
-                               $cmd = str_replace(
-                                       array( '$path/', '$width', '$height', '$input', '$output' ),
-                                       array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
-                                                  intval( $width ),
-                                                  intval( $height ),
-                                                  wfEscapeShellArg( $source ),
-                                                  wfEscapeShellArg( $destination ) ),
-                                       $wgSVGConverters[$wgSVGConverter] ) . " 2>&1";
-                               wfProfileIn( 'rsvg' );
-                               wfDebug( "reallyRenderThumb SVG: $cmd\n" );
-                               $err = wfShellExec( $cmd, $retval );
-                               wfProfileOut( 'rsvg' );
-                       }
-               } elseif ( $mime === "image/vnd.djvu" && $wgDjvuRenderer ) {
-                       // DJVU image
-                       // The file contains several images. First, extract the
-                       // page in hi-res, if it doesn't yet exist. Then, thumbnail
-                       // it.
-
-                       $cmd = wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size=${width}x${height} " .
-                               wfEscapeShellArg( $source );
-                       if ( $wgDjvuPostProcessor ) {
-                               $cmd .= " | {$wgDjvuPostProcessor}";
-                       }
-                       $cmd .= ' > ' . wfEscapeShellArg($destination);
-                       wfProfileIn( 'ddjvu' );
-                       wfDebug( "reallyRenderThumb DJVU: $cmd\n" );
-                       $err = wfShellExec( $cmd, $retval );
-                       wfProfileOut( 'ddjvu' );
-
-               } elseif ( $wgUseImageMagick ) {
-                       # use ImageMagick
-
-                       if ( $mime == 'image/jpeg' ) {
-                               $quality = "-quality 80"; // 80%
-                       } elseif ( $mime == 'image/png' ) {
-                               $quality = "-quality 95"; // zlib 9, adaptive filtering
-                       } else {
-                               $quality = ''; // default
-                       }
+       }
 
-                       # Specify white background color, will be used for transparent images
-                       # in Internet Explorer/Windows instead of default black.
-
-                       # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
-                       # It seems that ImageMagick has a bug wherein it produces thumbnails of
-                       # the wrong size in the second case.
-
-                       $cmd  =  wfEscapeShellArg($wgImageMagickConvertCommand) .
-                               " {$quality} -background white -size {$width} ".
-                               wfEscapeShellArg($source) .
-                               // Coalesce is needed to scale animated GIFs properly (bug 1017).
-                               ' -coalesce ' .
-                               // For the -resize option a "!" is needed to force exact size,
-                               // or ImageMagick may decide your ratio is wrong and slice off
-                               // a pixel.
-                               " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) .
-                               " -depth 8 " .
-                               wfEscapeShellArg($destination) . " 2>&1";
-                       wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
-                       wfProfileIn( 'convert' );
-                       $err = wfShellExec( $cmd, $retval );
-                       wfProfileOut( 'convert' );
-               } elseif( $wgCustomConvertCommand ) {
-                       # Use a custom convert command
-                       # Variables: %s %d %w %h
-                       $src = wfEscapeShellArg( $source );
-                       $dst = wfEscapeShellArg( $destination );
-                       $cmd = $wgCustomConvertCommand;
-                       $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
-                       $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
-                       wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
-                       wfProfileIn( 'convert' );
-                       $err = wfShellExec( $cmd, $retval );
-                       wfProfileOut( 'convert' );
-               } else {
-                       # Use PHP's builtin GD library functions.
-                       #
-                       # First find out what kind of file this is, and select the correct
-                       # input routine for this.
-
-                       $typemap = array(
-                               'image/gif'          => array( 'imagecreatefromgif',  'palette',   'imagegif'  ),
-                               'image/jpeg'         => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
-                               'image/png'          => array( 'imagecreatefrompng',  'bits',      'imagepng'  ),
-                               'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette',   'imagewbmp'  ),
-                               'image/xbm'          => array( 'imagecreatefromxbm',  'palette',   'imagexbm'  ),
-                       );
-                       if( !isset( $typemap[$mime] ) ) {
-                               $err = 'Image type not supported';
-                               wfDebug( "$err\n" );
-                               return $err;
-                       }
-                       list( $loader, $colorStyle, $saveType ) = $typemap[$mime];
+       /**
+        * Get a MediaHandler instance for this image
+        */
+       function getHandler() {
+               return MediaHandler::getHandler( $this->getMimeType() );
+       }
 
-                       if( !function_exists( $loader ) ) {
-                               $err = "Incomplete GD library configuration: missing function $loader";
-                               wfDebug( "$err\n" );
-                               return $err;
-                       }
+       /**
+        * Get a ThumbnailImage representing a file type icon
+        * @return ThumbnailImage
+        */
+       function iconThumb() {
+               global $wgStylePath, $wgStyleDirectory;
 
-                       $src_image = call_user_func( $loader, $source );
-                       $dst_image = imagecreatetruecolor( $width, $height );
-                       imagecopyresampled( $dst_image, $src_image,
-                                               0,0,0,0,
-                                               $width, $height, imagesx( $src_image ), imagesy( $src_image ) );
-                       call_user_func( $saveType, $dst_image, $destination );
-                       imagedestroy( $dst_image );
-                       imagedestroy( $src_image );
-               }
-
-               #
-               # Check for zero-sized thumbnails. Those can be generated when
-               # no disk space is available or some other error occurs
-               #
-               $removed = false;
-               if( file_exists( $destination ) ) {
-                       $thumbstat = stat( $destination );
-                       if( $thumbstat['size'] == 0 || $retval != 0 ) {
-                               wfDebugLog( 'thumbnail',
-                                       sprintf( 'Removing bad %d-byte thumbnail "%s"',
-                                               $thumbstat['size'], $destination ) );
-                               unlink( $destination );
-                               $removed = true;
+               $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
+               foreach( $try as $icon ) {
+                       $path = '/common/images/icons/' . $icon;
+                       $filepath = $wgStyleDirectory . $path;
+                       if( file_exists( $filepath ) ) {
+                               return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
                        }
                }
-               if ( $retval != 0 || $removed ) {
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
-                                       wfHostname(), $retval, trim($err), $cmd ) );
-                       return wfMsg( 'thumbnail_error', $err );
-               } else {
-                       return true;
-               }
+               return null;
        }
 
+       /**
+        * Get last thumbnailing error.
+        * Largely obsolete.
+        */
        function getLastError() {
                return $this->lastError;
        }
 
-       static function imageJpegWrapper( $dst_image, $thumbPath ) {
-               imageinterlace( $dst_image );
-               imagejpeg( $dst_image, $thumbPath, 95 );
-       }
-
        /**
         * Get all thumbnail names previously generated for this image
         */
@@ -1328,7 +1042,7 @@ class Image
         */
        function purgeMetadataCache() {
                clearstatcache();
-               $this->loadFromFile();
+               $this->upgradeRow();
                $this->saveToCache();
        }
 
@@ -1348,7 +1062,7 @@ class Image
                foreach ( $files as $file ) {
                        $m = array();
                        if ( preg_match( '/^(\d+)px/', $file, $m ) ) {
-                               list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] );
+                               $url = $this->thumbUrl( $m[1] );
                                $urls[] = $url;
                                @unlink( "$dir/$file" );
                        }
@@ -1391,6 +1105,9 @@ class Image
                $update->doUpdate();
        }
 
+       /**
+        * Check the image table schema on the given connection for subtle problems
+        */
        function checkDBSchema(&$db) {
                static $checkDone = false;
                global $wgCheckDBSchema;
@@ -1711,74 +1428,22 @@ class Image
                return $retVal;
        }
 
-       /**
-        * Retrive Exif data from the file and prune unrecognized tags
-        * and/or tags with invalid contents
-        *
-        * @param $filename
-        * @return array
-        */
-       private function retrieveExifData( $filename ) {
-               global $wgShowEXIF;
-
-               /*
-               if ( $this->getMimeType() !== "image/jpeg" )
-                       return array();
-               */
-
-               if( $wgShowEXIF && file_exists( $filename ) ) {
-                       $exif = new Exif( $filename );
-                       return $exif->getFilteredData();
-               }
-
-               return array();
-       }
-
        function getExifData() {
                global $wgRequest;
-               if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' )
+               $handler = $this->getHandler();
+               if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
                        return array();
-
-               $purge = $wgRequest->getVal( 'action' ) == 'purge';
-               $ret = unserialize( $this->metadata );
-
-               $oldver = isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ? $ret['MEDIAWIKI_EXIF_VERSION'] : 0;
-               $newver = Exif::version();
-
-               if ( !count( $ret ) || $purge || $oldver != $newver ) {
-                       $this->purgeMetadataCache();
-                       $this->updateExifData( $newver );
                }
-               if ( isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) )
-                       unset( $ret['MEDIAWIKI_EXIF_VERSION'] );
-               $format = new FormatExif( $ret );
-
-               return $format->getFormattedData();
-       }
-
-       function updateExifData( $version ) {
-               if ( $this->getImagePath() === false ) # Not a local image
-                       return;
-
-               # Get EXIF data from image
-               $exif = $this->retrieveExifData( $this->imagePath );
-               if ( count( $exif ) ) {
-                       $exif['MEDIAWIKI_EXIF_VERSION'] = $version;
-                       $this->metadata = serialize( $exif );
-               } else {
-                       $this->metadata = '0';
+               if ( !$this->metadata ) {
+                       return array();
                }
+               $exif = unserialize( $this->metadata );
+               if ( !$exif ) {
+                       return array();
+               }
+               $format = new FormatExif( $exif );
 
-               # Update EXIF data in database
-               $dbw = wfGetDB( DB_MASTER );
-
-               $this->checkDBSchema($dbw);
-
-               $dbw->update( 'image',
-                       array( 'img_metadata' => $this->metadata ),
-                       array( 'img_name' => $this->name ),
-                       __METHOD__
-               );
+               return $format->getFormattedData();
        }
 
        /**
@@ -2145,12 +1810,17 @@ class Image
                                        // an archived file revision.
                                        if( is_null( $row->fa_metadata ) ) {
                                                $tempFile = $store->filePath( $row->fa_storage_key );
-                                               $metadata = serialize( $this->retrieveExifData( $tempFile ) );
 
                                                $magic = MimeMagic::singleton();
                                                $mime = $magic->guessMimeType( $tempFile, true );
                                                $media_type = $magic->getMediaType( $tempFile, $mime );
                                                list( $major_mime, $minor_mime ) = self::splitMime( $mime );
+                                               $handler = MediaHandler::getHandler( $mime );
+                                               if ( $handler ) {
+                                                       $metadata = $handler->getMetadata( $image, $tempFile );
+                                               } else {
+                                                       $metadata = '';
+                                               }
                                        } else {
                                                $metadata   = $row->fa_metadata;
                                                $major_mime = $row->fa_major_mime;
@@ -2257,64 +1927,6 @@ class Image
                return $revisions;
        }
 
-       /**
-        * Select a page from a multipage document. Determines the page used for
-        * rendering thumbnails.
-        *
-        * @param $page Integer: page number, starting with 1
-        */
-       function selectPage( $page ) {
-               if( $this->initializeMultiPageXML() ) {
-                       wfDebug( __METHOD__." selecting page $page \n" );
-                       $this->page = $page;
-                       $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1];
-                       $this->height = intval( $o['height'] );
-                       $this->width = intval( $o['width'] );
-               } else {
-                       wfDebug( __METHOD__." selectPage($page) for bogus multipage xml on '$this->name'\n" );
-                       return;
-               }
-       }
-
-       /**
-        * Lazy-initialize multipage XML metadata for DjVu files.
-        * @return bool true if $this->multiPageXML is set up and ready;
-        *              false if corrupt or otherwise failing
-        */
-       function initializeMultiPageXML() {
-               $this->load();
-               if ( isset( $this->multiPageXML ) ) {
-                       return true;
-               }
-
-               #
-               # Check for files uploaded prior to DJVU support activation,
-               # or damaged.
-               #
-               if( empty( $this->metadata ) || $this->metadata == serialize( array() ) ) {
-                       $deja = new DjVuImage( $this->imagePath );
-                       $this->metadata = $deja->retrieveMetaData();
-                       $this->purgeMetadataCache();
-
-                       # Update metadata in the database
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update( 'image',
-                               array( 'img_metadata' => $this->metadata ),
-                               array( 'img_name' => $this->name ),
-                               __METHOD__
-                       );
-               }
-               wfSuppressWarnings();
-               try {
-                       $this->multiPageXML = new SimpleXMLElement( $this->metadata );
-               } catch( Exception $e ) {
-                       wfDebug( "Bogus multipage XML metadata on '$this->name'\n" );
-                       $this->multiPageXML = null;
-               }
-               wfRestoreWarnings();
-               return isset( $this->multiPageXML );
-       }
-
        /**
         * Returns 'true' if this image is a multipage document, e.g. a DJVU
         * document.
@@ -2322,7 +1934,8 @@ class Image
         * @return Bool
         */
        function isMultipage() {
-               return ( $this->mime == 'image/vnd.djvu' );
+               $handler = $this->getHandler();
+               return $handler && $handler->isMultiPage();
        }
 
        /**
@@ -2330,13 +1943,10 @@ class Image
         * documents which aren't multipage documents
         */
        function pageCount() {
-               if ( ! $this->isMultipage() ) {
-                       return null;
-               }
-               if( $this->initializeMultiPageXML() ) {
-                       return count( $this->multiPageXML->xpath( '//OBJECT' ) );
+               $handler = $this->getHandler();
+               if ( $handler && $handler->isMultiPage() ) {
+                       return $handler->pageCount( $this );
                } else {
-                       wfDebug( "Requested pageCount() for bogus multi-page metadata for '$this->name'\n" );
                        return null;
                }
        }
@@ -2358,7 +1968,11 @@ class Image
         */
        static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
                // Exact integer multiply followed by division
-               return round( $srcHeight * $dstWidth / $srcWidth );
+               if ( $srcWidth == 0 ) {
+                       return 0;
+               } else {
+                       return round( $srcHeight * $dstWidth / $srcWidth );
+               }
        }
 
        /**
@@ -2366,28 +1980,11 @@ class Image
         * can't be determined.
         *
         * @param string $fileName The filename
-        * @param string $mimeType The MIME type of the file
-        * @param object $deja Filled with a DjVu object if the mime type is image/vnd.djvu
         * @return array
         */
-       static function getImageSize( $fileName, $mimeType, &$deja ) {
-               $magic =& MimeMagic::singleton();
-               if( $mimeType == 'image/svg' || $mimeType == 'image/svg+xml' ) {
-                       $gis = wfGetSVGsize( $fileName );
-               } elseif( $mimeType == 'image/vnd.djvu' ) {
-                       wfSuppressWarnings();
-                       $deja = new DjVuImage( $fileName );
-                       $gis = $deja->getImageSize();
-                       wfRestoreWarnings();
-               } elseif ( !$magic->isPHPImageType( $mimeType ) ) {
-                       # Don't try to get the width and height of sound and video files, that's bad for performance
-                       $gis = false;
-               } else {
-                       wfSuppressWarnings();
-                       $gis = getimagesize( $fileName );
-                       wfRestoreWarnings();
-               }
-               return $gis;
+       function getImageSize( $fileName ) {
+               $handler = $this->getHandler();
+               return $handler->getImageSize( $this, $fileName );
        }
 
        /**
@@ -2395,22 +1992,14 @@ class Image
         * @return array thumbnail extension and MIME type
         */
        static function getThumbType( $ext, $mime ) {
-               switch ( $mime ) {
-                       case 'image/svg':
-                       case 'image/svg+xml':
-                               $ext = 'png';
-                               $mime = 'image/png';
-                               break;
-                       case 'image/vnd.djvu':
-                               $ext = $GLOBALS['wgDjvuOutputExtension'];
-                               $magic = MimeMagic::singleton();
-                               $mime = $magic->guessTypesForExtension( $ext );
-                               break;
+               $handler = MediaHandler::getHandler( $mime );
+               if ( $handler ) {
+                       return $handler->getThumbType( $ext, $mime );
+               } else {
+                       return array( $ext, $mime );
                }
-               return array( $ext, $mime );
        }
 
-
 } //class
 
 class ArchivedFile
@@ -2519,60 +2108,6 @@ class ArchivedFile
        }
 }
 
-/**
- * Wrapper class for thumbnail images
- */
-class ThumbnailImage {
-       /**
-        * @param string $path Filesystem path to the thumb
-        * @param string $url URL path to the thumb
-        * @private
-        */
-       function ThumbnailImage( $url, $width, $height, $path = false ) {
-               $this->url = $url;
-               $this->width = round( $width );
-               $this->height = round( $height );
-                       # These should be integers when they get here.
-                       # If not, there's a bug somewhere.  But let's at
-                       # least produce valid HTML code regardless.
-               $this->path = $path;
-       }
-
-       /**
-        * @return string The thumbnail URL
-        */
-       function getUrl() {
-               return $this->url;
-       }
-
-       /**
-        * Return HTML <img ... /> tag for the thumbnail, will include
-        * width and height attributes and a blank alt text (as required).
-        *
-        * You can set or override additional attributes by passing an
-        * associative array of name => data pairs. The data will be escaped
-        * for HTML output, so should be in plaintext.
-        *
-        * @param array $attribs
-        * @return string
-        * @public
-        */
-       function toHtml( $attribs = array() ) {
-               $attribs['src'] = $this->url;
-               $attribs['width'] = $this->width;
-               $attribs['height'] = $this->height;
-               if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
-
-               $html = '<img ';
-               foreach( $attribs as $name => $data ) {
-                       $html .= $name . '="' . htmlspecialchars( $data ) . '" ';
-               }
-               $html .= '/>';
-               return $html;
-       }
-
-}
-
 /**
  * Aliases for backwards compatibility with 1.6
  */
index 86bf786..036d717 100644 (file)
@@ -183,7 +183,7 @@ class ImageGallery
         *
         */
        function toHTML() {
-               global $wgLang, $wgGenerateThumbnailOnParse;
+               global $wgLang;
 
                $sk = $this->getSkin();
 
@@ -191,6 +191,7 @@ class ImageGallery
                if( $this->mCaption )
                        $s .= "\n\t<caption>{$this->mCaption}</caption>";
 
+               $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
                $i = 0;
                foreach ( $this->mImages as $pair ) {
                        $img =& $pair[0];
@@ -206,7 +207,7 @@ class ImageGallery
                                # The image is blacklisted, just show it as a text link.
                                $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
                                        . $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>';
-                       } elseif( !( $thumb = $img->getThumbnail( $this->mWidths, $this->mHeights, $wgGenerateThumbnailOnParse ) ) ) {
+                       } elseif( !( $thumb = $img->transform( $params ) ) ) {
                                # Error generating thumbnail.
                                $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
                                        . htmlspecialchars( $img->getLastError() ) . '</div>';
index 8fc47c0..7d3414a 100644 (file)
@@ -166,11 +166,9 @@ class ImagePage extends Article {
 
        function openShowImage() {
                global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang;
-               global $wgUseImageResize, $wgGenerateThumbnailOnParse;
 
                $full_url  = $this->img->getURL();
-               $anchoropen = '';
-               $anchorclose = '';
+               $linkAttribs = false;
                $sizeSel = intval( $wgUser->getOption( 'imagesize') );
                if( !isset( $wgImageLimits[$sizeSel] ) ) {
                        $sizeSel = User::getDefaultOption( 'imagesize' );
@@ -190,10 +188,11 @@ class ImagePage extends Article {
                if ( $this->img->exists() ) {
                        # image
                        $page = $wgRequest->getIntOrNull( 'page' );
-                       if ( ! is_null( $page ) ) {
-                               $this->img->selectPage( $page );
-                       } else {
+                       if ( is_null( $page ) ) {
+                               $params = array();
                                $page = 1;
+                       } else {
+                               $params = array( 'page' => $page );
                        }
                        $width_orig = $this->img->getWidth();
                        $width = $width_orig;
@@ -201,6 +200,7 @@ class ImagePage extends Article {
                        $height = $height_orig;
                        $mime = $this->img->getMimeType();
                        $showLink = false;
+                       $linkAttribs = array( 'href' => $full_url );
 
                        if ( $this->img->allowInlineDisplay() and $width and $height) {
                                # image
@@ -223,39 +223,33 @@ class ImagePage extends Article {
                                                # Note that $height <= $maxHeight now, but might not be identical
                                                # because of rounding.
                                        }
+                               }
+                               $params['width'] = $width;
+                               $thumbnail = $this->img->transform( $params );
 
-                                       if( $wgUseImageResize ) {
-                                               $thumbnail = $this->img->getThumbnail( $width, -1, $wgGenerateThumbnailOnParse );
-                                               if ( $thumbnail == null ) {
-                                                       $url = $this->img->getViewURL();
-                                               } else {
-                                                       $url = $thumbnail->getURL();
-                                               }
-                                       } else {
-                                               # No resize ability? Show the full image, but scale
-                                               # it down in the browser so it fits on the page.
-                                               $url = $this->img->getViewURL();
-                                       }
-                                       $anchoropen  = "<a href=\"{$full_url}\">";
-                                       $anchorclose = "</a><br />";
-                                       if( $this->img->mustRender() ) {
-                                               $showLink = true;
-                                       } else {
-                                               $anchorclose .= wfMsg('show-big-image-thumb', $width, $height ) .
-                                                       '<br />' . "\n$anchoropen{$msgbig}</a> " . $msgsize;
-                                       }
-                               } else {
-                                       $url = $this->img->getViewURL();
+                               $anchorclose = "<br />";
+                               if( $this->img->mustRender() ) {
                                        $showLink = true;
+                               } else {
+                                       $anchorclose .= 
+                                               wfMsg('show-big-image-thumb', $width, $height ) .
+                                               '<br />' . Xml::tags( 'a', $linkAttribs,  $msgbig ) . ' ' . $msgsize;
                                }
 
                                if ( $this->img->isMultipage() ) {
                                        $wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
                                }
 
-                               $wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
-                                    "<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
-                                    htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' );
+                               $imgAttribs = array(
+                                       'border' => 0,
+                                       'alt' => $this->img->getTitle()->getPrefixedText()
+                               );
+
+                               if ( $thumbnail ) {
+                                       $wgOut->addHTML( '<div class="fullImageLink" id="file">' . 
+                                               $thumbnail->toHtml( $imgAttribs, $linkAttribs ) .
+                                               $anchorclose . '</div>' );
+                               }
 
                                if ( $this->img->isMultipage() ) {
                                        $count = $this->img->pageCount();
@@ -263,17 +257,17 @@ class ImagePage extends Article {
                                        if ( $page > 1 ) {
                                                $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
                                                $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page-1) );
-                                               $this->img->selectPage( $page - 1 );
-                                               $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
+                                               $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none', 
+                                                       array( 'page' => $page - 1 ) );
                                        } else {
                                                $thumb1 = '';
                                        }
 
                                        if ( $page < $count ) {
                                                $label = wfMsg( 'imgmultipagenext' );
-                                               $this->img->selectPage( $page + 1 );
                                                $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page+1) );
-                                               $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
+                                               $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none', 
+                                                       array( 'page' => $page + 1 ) );
                                        } else {
                                                $thumb2 = '';
                                        }
@@ -294,7 +288,7 @@ class ImagePage extends Article {
                                                htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>';
 
                                        $wgOut->addHTML( '</td><td><div class="multipageimagenavbox">' .
-                                          "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
+                                               "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
                                }
                        } else {
                                #if direct link is allowed but it's not a renderable image, show an icon.
index b216763..c34fa21 100644 (file)
@@ -429,25 +429,19 @@ class Linker {
        }
 
        /** @todo document */
-       function makeImageLinkObj( $nt, $label, $alt, $align = '', $width = false, $height = false, $framed = false,
-         $thumb = false, $manual_thumb = '', $page = null, $valign = '' )
+       function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
+         $thumb = false, $manual_thumb = '', $valign = '' )
        {
-               global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse;
+               global $wgContLang, $wgUser, $wgThumbLimits;
 
                $img   = new Image( $nt );
 
-               if ( ! is_null( $page ) ) {
-                       $img->selectPage( $page );
-               }
-
                if ( !$img->allowInlineDisplay() && $img->exists() ) {
                        return $this->makeKnownLinkObj( $nt );
                }
 
-               $url   = $img->getViewURL();
                $error = $prefix = $postfix = '';
-
-               wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" );
+               $page = isset( $params['page'] ) ? $params['page'] : false;
 
                if ( 'center' == $align )
                {
@@ -456,6 +450,16 @@ class Linker {
                        $align   = 'none';
                }
 
+               if ( !isset( $params['width'] ) ) {
+                       $wopt = $wgUser->getOption( 'thumbsize' );
+
+                       if( !isset( $wgThumbLimits[$wopt] ) ) {
+                                $wopt = User::getDefaultOption( 'thumbsize' );
+                       }
+
+                       $params['width'] = min( $img->getWidth( $page ), $wgThumbLimits[$wopt] );
+               }
+
                if ( $thumb || $framed ) {
 
                        # Create a thumbnail. Alignment depends on language
@@ -468,73 +472,39 @@ class Linker {
                        if ( $align == '' ) {
                                $align = $wgContLang->isRTL() ? 'left' : 'right';
                        }
-
-
-                       if ( $width === false ) {
-                               $wopt = $wgUser->getOption( 'thumbsize' );
-
-                               if( !isset( $wgThumbLimits[$wopt] ) ) {
-                                        $wopt = User::getDefaultOption( 'thumbsize' );
-                               }
-
-                               $width = min( $img->getWidth(), $wgThumbLimits[$wopt] );
-                       }
-
-                       return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $width, $height, $framed, $manual_thumb ).$postfix;
+                       return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
                }
 
-               if ( $width && $img->exists() ) {
-
-                       # Create a resized image, without the additional thumbnail
-                       # features
-
-                       if ( $height == false )
-                               $height = -1;
-                       if ( $manual_thumb == '') {
-                               $thumb = $img->getThumbnail( $width, $height, $wgGenerateThumbnailOnParse );
-                               if ( $thumb ) {
-                                       // In most cases, $width = $thumb->width or $height = $thumb->height.
-                                       // If not, we're scaling the image larger than it can be scaled,
-                                       // so we send to the browser a smaller thumbnail, and let the client do the scaling.
-
-                                       if ($height != -1 && $width > $thumb->width * $height / $thumb->height) {
-                                               // $height is the limiting factor, not $width
-                                               // set $width to the largest it can be, such that the resulting
-                                               // scaled height is at most $height
-                                               $width = floor($thumb->width * $height / $thumb->height);
-                                       }
-                                       $height = round($thumb->height * $width / $thumb->width);
+               if ( $params['width'] && $img->exists() ) {
+                       # Create a resized image, without the additional thumbnail features
+                       $thumb = $img->transform( $params );
+               } else {
+                       $thumb = false;
+               }
 
-                                       wfDebug( "makeImageLinkObj: client-size set to '$width x $height'\n" );
-                                       $url = $thumb->getUrl();
-                               } else {
-                                       $error = htmlspecialchars( $img->getLastError() );
-                                       // Do client-side scaling...
-                                       $height = intval( $img->getHeight() * $width / $img->getWidth() );
-                               }
-                       }
+               if ( $page ) {
+                       $query = 'page=' . urlencode( $page );
                } else {
-                       $width = $img->width;
-                       $height = $img->height;
+                       $query = '';
+               }
+               $u = $nt->getLocalURL( $query );
+               $imgAttribs = array(
+                       'alt' => $alt,
+                       'longdesc' => $u
+               );
+               if ( $valign ) {
+                       $imgAttribs['style'] = "vertical-align: $valign";
                }
+               $linkAttribs = array(
+                       'href' => $u,
+                       'class' => 'image',
+                       'title' => $alt
+               );
 
-               wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" );
-               $u = $nt->escapeLocalURL();
-               if ( $error ) {
-                       $s = $error;
-               } elseif ( $url == '' ) {
+               if ( !$thumb ) {
                        $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
-                       //$s .= "<br />{$alt}<br />{$url}<br />\n";
                } else {
-                       $s = '<a href="'.$u.'" class="image" title="'.$alt.'">' .
-                                '<img src="'.$url.'" alt="'.$alt.'" ' .
-                                ( $width
-                                       ? ( 'width="'.$width.'" height="'.$height.'" ' )
-                                       : '' ) .
-                                ( $valign
-                                       ? ( 'style="vertical-align: '.$valign.'" ' )
-                                       : '' ) .
-                                'longdesc="'.$u.'" /></a>';
+                       $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
                }
                if ( '' != $align ) {
                        $s = "<div class=\"float{$align}\"><span>{$s}</span></div>";
@@ -546,86 +516,64 @@ class Linker {
         * Make HTML for a thumbnail including image, border and caption
         * $img is an Image object
         */
-       function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) {
-               global $wgStylePath, $wgContLang, $wgGenerateThumbnailOnParse;
+       function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
+               global $wgStylePath, $wgContLang;
                $thumbUrl = '';
                $error = '';
 
-               $width = $height = 0;
-               if ( $img->exists() ) {
-                       $width  = $img->getWidth();
-                       $height = $img->getHeight();
-               }
-               if ( 0 == $width || 0 == $height ) {
-                       $width = $height = 180;
-               }
-               if ( $boxwidth == 0 ) {
-                       $boxwidth = 180;
+               $page = isset( $params['page'] ) ? $params['page'] : false;
+
+               if ( empty( $params['width'] ) ) {
+                       $params['width'] = 180;
                }
-               if ( $framed ) {
+               $thumb = false;
+               if ( $manual_thumb != '' ) {
+                       # Use manually specified thumbnail
+                       $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
+                       if( $manual_title ) {
+                               $manual_img = new Image( $manual_title );
+                               $thumb = $manual_img->getUnscaledThumb();
+                       }
+               } elseif ( $framed ) {
                        // Use image dimensions, don't scale
-                       $boxwidth  = $width;
-                       $boxheight = $height;
-                       $thumbUrl  = $img->getViewURL();
+                       $thumb = $img->getUnscaledThumb( $page );
                } else {
-                       if ( $boxheight === false )
-                               $boxheight = -1;
-                       if ( '' == $manual_thumb ) {
-                               $thumb = $img->getThumbnail( $boxwidth, $boxheight, $wgGenerateThumbnailOnParse );
-                               if ( $thumb ) {
-                                       $thumbUrl = $thumb->getUrl();
-                                       $boxwidth = $thumb->width;
-                                       $boxheight = $thumb->height;
-                               } else {
-                                       $error = $img->getLastError();
-                               }
-                       }
+                       $thumb = $img->transform( $params );
                }
-               $oboxwidth = $boxwidth + 2;
 
-               if ( $manual_thumb != '' ) # Use manually specified thumbnail
-               {
-                       $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $manual_thumb ) ;
-                       if( $manual_title ) {
-                               $manual_img = new Image( $manual_title );
-                               $thumbUrl = $manual_img->getViewURL();
-                               if ( $manual_img->exists() )
-                               {
-                                       $width  = $manual_img->getWidth();
-                                       $height = $manual_img->getHeight();
-                                       $boxwidth = $width ;
-                                       $boxheight = $height ;
-                                       $oboxwidth = $boxwidth + 2 ;
-                               }
-                       }
+               if ( $thumb ) {
+                       $outerWidth = $thumb->getWidth() + 2;
+               } else {
+                       $outerWidth = $params['width'] + 2;
                }
 
-               $u = $img->getEscapeLocalURL();
+               $query = $page ? 'page=' . urlencode( $page ) : '';
+               $u = $img->getTitle()->getLocalURL( $query );
 
                $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
                $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
                $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
 
-               $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">";
-               if( $thumbUrl == '' ) {
-                       // Couldn't generate thumbnail? Scale the image client-side.
-                       $thumbUrl = $img->getViewURL();
-                       if( $boxheight == -1 ) {
-                               // Approximate...
-                               $boxheight = round( $height * $boxwidth / $width );
-                       }
-               }
-               if ( $error ) {
-                       $s .= htmlspecialchars( $error );
+               $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
+               if ( !$thumb ) {
+                       $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
                        $zoomicon = '';
                } elseif( !$img->exists() ) {
                        $s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
                        $zoomicon = '';
                } else {
-                       $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
-                               '<img src="'.$thumbUrl.'" alt="'.$alt.'" ' .
-                               'width="'.$boxwidth.'" height="'.$boxheight.'" ' .
-                               'longdesc="'.$u.'" class="thumbimage" /></a>';
+                       $imgAttribs = array(
+                               'alt' => $alt,
+                               'longdesc' => $u,
+                               'class' => 'thumbimage'
+                       );
+                       $linkAttribs = array(
+                               'href' => $u,
+                               'title' => $alt,
+                               'class' => 'internal'
+                       );
+                               
+                       $s .= $thumb->toHtml( $imgAttribs, $linkAttribs );
                        if ( $framed ) {
                                $zoomicon="";
                        } else {
diff --git a/includes/MediaTransformOutput.php b/includes/MediaTransformOutput.php
new file mode 100644 (file)
index 0000000..5004fcc
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * Base class for the output of MediaHandler::doTransform() and Image::transform().
+ */
+abstract class MediaTransformOutput {
+       /**
+        * Get the width of the output box
+        */
+       function getWidth() {
+               return $this->width;
+       } 
+
+       /**
+        * Get the height of the output box
+        */
+       function getHeight() {
+               return $this->height;
+       }
+
+       /**
+        * @return string The thumbnail URL
+        */
+       function getUrl() {
+               return $this->url;
+       }
+
+       /**
+        * @return string Destination file path (local filesystem)
+        */
+       function getPath() {
+               return $this->path;
+       }
+
+       /**
+        * Fetch HTML for this transform output
+        * @param array $attribs Advisory associative array of HTML attributes supplied 
+        *    by the linker. These can be incorporated into the output in any way.
+        * @param array $linkAttribs Attributes of a suggested enclosing <a> tag. 
+        *    May be ignored.
+        */
+       abstract function toHtml( $attribs = array() , $linkAttribs = false );
+
+       /**
+        * This will be overridden to return true in error classes
+        */
+       function isError() {
+               return false;
+       }
+
+       /**
+        * Wrap some XHTML text in an anchor tag with the given attributes
+        */
+       protected function linkWrap( $linkAttribs, $contents ) {
+               if ( $linkAttribs ) {
+                       return Xml::tags( 'a', $linkAttribs, $contents );
+               } else {
+                       return $contents;
+               }
+       }
+}
+
+
+/**
+ * Media transform output for images
+ */
+class ThumbnailImage extends MediaTransformOutput {
+       /**
+        * @param string $path Filesystem path to the thumb
+        * @param string $url URL path to the thumb
+        * @private
+        */
+       function ThumbnailImage( $url, $width, $height, $path = false ) {
+               $this->url = $url;
+               # These should be integers when they get here.
+               # If not, there's a bug somewhere.  But let's at
+               # least produce valid HTML code regardless.
+               $this->width = round( $width );
+               $this->height = round( $height );
+               $this->path = $path;
+       }
+
+       /**
+        * Return HTML <img ... /> tag for the thumbnail, will include
+        * width and height attributes and a blank alt text (as required).
+        *
+        * You can set or override additional attributes by passing an
+        * associative array of name => data pairs. The data will be escaped
+        * for HTML output, so should be in plaintext.
+        *
+        * If $linkAttribs is given, the image will be enclosed in an <a> tag.
+        *
+        * @param array $attribs
+        * @param array $linkAttribs
+        * @return string
+        * @public
+        */
+       function toHtml( $attribs = array(), $linkAttribs = false ) {
+               $attribs['src'] = $this->url;
+               $attribs['width'] = $this->width;
+               $attribs['height'] = $this->height;
+               if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
+               return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
+       }
+
+}
+
+/**
+ * Basic media transform error class
+ */
+class MediaTransformError extends MediaTransformOutput {
+       var $htmlMsg, $textMsg, $width, $height, $url, $path;
+
+       function __construct( $msg, $width, $height /*, ... */ ) {
+               $args = array_slice( func_get_args(), 3 );
+               $htmlArgs = array_map( 'htmlspecialchars', $args );
+               $htmlArgs = array_map( 'nl2br', $htmlArgs );
+
+               $this->htmlMsg = wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $msg, true ) ), $htmlArgs );
+               $this->textMsg = wfMsgReal( $msg, $args );
+               $this->width = intval( $width );
+               $this->height = intval( $height );
+               $this->url = false;
+               $this->path = false;
+       }
+
+       function toHtml( $attribs = array(), $linkAttribs = false ) {
+               return "<table class=\"MediaTransformError\" style=\"" .
+                       "width: {$this->width}px; height: {$this->height}px;\"><tr><td>" .
+                       $this->htmlMsg .
+                       "</td></tr></table>";
+       }
+
+       function toText() {
+               return $this->textMsg;
+       }
+
+       function getHtmlMsg() {
+               return $this->htmlMsg;
+       }
+
+       function isError() {
+               return true;
+       }
+}
+
+/**
+ * Shortcut class for parameter validation errors
+ */
+class TransformParameterError extends MediaTransformError {
+       function __construct( $params ) {
+               parent::__construct( 'thumbnail_error', 
+                       max( @$params['width'], 180 ), max( @$params['height'], 180 ), 
+                       wfMsg( 'thumbnail_invalid_params' ) );
+       }
+}
+
+?>
index 9142cce..516a3cd 100644 (file)
@@ -22,7 +22,7 @@ image/x-bmp bmp
 image/gif gif
 image/jpeg jpeg jpg jpe
 image/png png
-image/svg+xml svg
+image/svg+xml image/svg svg
 image/tiff tiff tif
 image/vnd.djvu djvu
 image/x-portable-pixmap ppm
@@ -51,7 +51,7 @@ image/x-bmp image/bmp [BITMAP]
 image/gif [BITMAP]
 image/jpeg [BITMAP]
 image/png [BITMAP]
-image/svg image/svg+xml [DRAWING]
+image/svg+xml [DRAWING]
 image/tiff [BITMAP]
 image/vnd.djvu [BITMAP]
 image/x-portable-pixmap [BITMAP]
index 89ab091..7f34fa1 100644 (file)
@@ -4387,8 +4387,8 @@ class Parser
         * Parse image options text and use it to make an image
         */
        function makeImage( $nt, $options ) {
-               global $wgUseImageResize, $wgDjvuRenderer;
-
+               # @TODO: let the MediaHandler specify its transform parameters
+               #
                # Check if the options text is of the form "options|alt text"
                # Options are:
                #  * thumbnail          make a thumbnail with enlarge-icon and caption, alignment depends on lang
@@ -4408,6 +4408,7 @@ class Parser
                #  * bottom
                #  * text-bottom
 
+
                $part = array_map( 'trim', explode( '|', $options) );
 
                $mwAlign = array();
@@ -4422,13 +4423,14 @@ class Parser
                $mwPage   =& MagicWord::get( 'img_page' );
                $caption = '';
 
-               $width = $height = $framed = $thumb = false;
-               $page = null;
+               $params = array();
+               $framed = $thumb = false;
                $manual_thumb = '' ;
                $align = $valign = '';
+               $sk = $this->mOptions->getSkin();
 
                foreach( $part as $val ) {
-                       if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
+                       if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
                                $thumb=true;
                        } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
                                # use manually specified thumbnail
@@ -4446,19 +4448,18 @@ class Parser
                                                continue 2;
                                        }
                                }
-                               if ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer
-                               && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
+                               if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
                                        # Select a page in a multipage document
-                                       $page = $match;
-                               } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
+                                       $params['page'] = $match;
+                               } elseif ( !isset( $params['width'] ) && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
                                        wfDebug( "img_width match: $match\n" );
                                        # $match is the image width in pixels
                                        $m = array();
                                        if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
-                                               $width = intval( $m[1] );
-                                               $height = intval( $m[2] );
+                                                $params['width'] = intval( $m[1] );
+                                                $params['height'] = intval( $m[2] );
                                        } else {
-                                               $width = intval($match);
+                                               $params['width'] = intval($match);
                                        }
                                } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
                                        $framed=true;
@@ -4477,8 +4478,7 @@ class Parser
                $alt = Sanitizer::stripAllTags( $alt );
 
                # Linker does the rest
-               $sk = $this->mOptions->getSkin();
-               return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page, $valign );
+               return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign );
        }
 
        /**
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
new file mode 100644 (file)
index 0000000..02e665a
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+
+class BitmapHandler extends ImageHandler {
+       function normaliseParams( $image, &$params ) {
+               global $wgMaxImageArea;
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+
+               $mimeType = $image->getMimeType();
+               $srcWidth = $image->getWidth( $params['page'] );
+               $srcHeight = $image->getHeight( $params['page'] );
+
+               # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
+               # 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 )
+               {
+                       return false;
+               }
+
+               # Don't make an image bigger than the source
+               $params['physicalWidth'] = $params['width'];
+               $params['physicalHeight'] = $params['height'];
+               
+               if ( $params['physicalWidth'] >= $srcWidth ) {
+                       $params['physicalWidth'] = $srcWidth;
+                       $params['physicalHeight'] = $srcHeight;
+                       return true;
+               }
+
+               return true;
+       }
+       
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               global $wgUseImageMagick, $wgImageMagickConvertCommand;
+               global $wgCustomConvertCommand;
+               global $wgSharpenParameter, $wgSharpenReductionThreshold;
+
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $physicalWidth = $params['physicalWidth'];
+               $physicalHeight = $params['physicalHeight'];
+               $clientWidth = $params['width'];
+               $clientHeight = $params['height'];
+               $srcWidth = $image->getWidth();
+               $srcHeight = $image->getHeight();
+               $mimeType = $image->getMimeType();
+               $srcPath = $image->getImagePath();
+               $retval = 0;
+               wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
+
+               if ( $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
+                       # normaliseParams (or the user) wants us to return the unscaled image
+                       wfDebug( __METHOD__.": returning unscaled image\n" );
+                       return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
+               }
+
+               if ( $wgUseImageMagick ) {
+                       $scaler = 'im';
+               } elseif ( $wgCustomConvertCommand ) {
+                       $scaler = 'custom';
+               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
+                       $scaler = 'gd';
+               } else {
+                       $scaler = 'client';
+               }
+
+               if ( $scaler == 'client' ) {
+                       # Client-side image scaling, use the source URL
+                       # Using the destination URL in a TRANSFORM_LATER request would be incorrect
+                       return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
+               }
+
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, 
+                               wfMsg( 'thumbnail_dest_directory' ) );
+               }
+
+               if ( $scaler == 'im' ) {
+                       # use ImageMagick
+
+                       $sharpen = '';
+                       if ( $mimeType == 'image/jpeg' ) {
+                               $quality = "-quality 80"; // 80%
+                               # Sharpening, see bug 6193
+                               if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
+                                       $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
+                               }
+                       } elseif ( $mimeType == 'image/png' ) {
+                               $quality = "-quality 95"; // zlib 9, adaptive filtering
+                       } else {
+                               $quality = ''; // default
+                       }
+
+                       # Specify white background color, will be used for transparent images
+                       # in Internet Explorer/Windows instead of default black.
+
+                       # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
+                       # It seems that ImageMagick has a bug wherein it produces thumbnails of
+                       # the wrong size in the second case.
+
+                       $cmd  =  wfEscapeShellArg($wgImageMagickConvertCommand) .
+                               " {$quality} -background white -size {$physicalWidth} ".
+                               wfEscapeShellArg($srcPath) .
+                               // Coalesce is needed to scale animated GIFs properly (bug 1017).
+                               ' -coalesce ' .
+                               // For the -resize option a "!" is needed to force exact size,
+                               // or ImageMagick may decide your ratio is wrong and slice off
+                               // a pixel.
+                               " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
+                               " -depth 8 $sharpen " .
+                               wfEscapeShellArg($dstPath) . " 2>&1";
+                       wfDebug( __METHOD__.": running ImageMagick: $cmd\n");
+                       wfProfileIn( 'convert' );
+                       $err = wfShellExec( $cmd, $retval );
+                       wfProfileOut( 'convert' );
+               } elseif( $scaler == 'custom' ) {
+                       # Use a custom convert command
+                       # Variables: %s %d %w %h
+                       $src = wfEscapeShellArg( $srcPath );
+                       $dst = wfEscapeShellArg( $dstPath );
+                       $cmd = $wgCustomConvertCommand;
+                       $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+                       $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
+                       wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
+                       wfProfileIn( 'convert' );
+                       $err = wfShellExec( $cmd, $retval );
+                       wfProfileOut( 'convert' );
+               } else /* $scaler == 'gd' */ {
+                       # Use PHP's builtin GD library functions.
+                       #
+                       # First find out what kind of file this is, and select the correct
+                       # input routine for this.
+
+                       $typemap = array(
+                               'image/gif'          => array( 'imagecreatefromgif',  'palette',   'imagegif'  ),
+                               'image/jpeg'         => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
+                               'image/png'          => array( 'imagecreatefrompng',  'bits',      'imagepng'  ),
+                               'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette',   'imagewbmp'  ),
+                               'image/xbm'          => array( 'imagecreatefromxbm',  'palette',   'imagexbm'  ),
+                       );
+                       if( !isset( $typemap[$mimeType] ) ) {
+                               $err = 'Image type not supported';
+                               wfDebug( "$err\n" );
+                               return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+                       }
+                       list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
+
+                       if( !function_exists( $loader ) ) {
+                               $err = "Incomplete GD library configuration: missing function $loader";
+                               wfDebug( "$err\n" );
+                               return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+                       }
+
+                       $src_image = call_user_func( $loader, $srcPath );
+                       $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
+                       imagecopyresampled( $dst_image, $src_image,
+                                               0,0,0,0,
+                                               $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
+                       call_user_func( $saveType, $dst_image, $dstPath );
+                       imagedestroy( $dst_image );
+                       imagedestroy( $src_image );
+                       $retval = 0;
+               }
+
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
+                                       wfHostname(), $retval, trim($err), $cmd ) );
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+               } else {
+                       return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
+               }
+       }
+
+       static function imageJpegWrapper( $dst_image, $thumbPath ) {
+               imageinterlace( $dst_image );
+               imagejpeg( $dst_image, $thumbPath, 95 );
+       }
+
+
+       function getMetadata( $image, $filename ) {
+               global $wgShowEXIF;
+               if( $wgShowEXIF && file_exists( $filename ) ) {
+                       $exif = new Exif( $filename );
+                       return serialize( $exif->getFilteredData() );
+               } else {
+                       return '';
+               }
+       }
+
+       function getMetadataType( $image ) {
+               return 'exif';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               global $wgShowEXIF;
+               if ( !$wgShowEXIF ) {
+                       # Metadata disabled and so an empty field is expected
+                       return true;
+               }
+               if ( $metadata === '0' ) {
+                       # Special value indicating that there is no EXIF data in the file
+                       return true;
+               }
+               $exif = @unserialize( $metadata );
+               if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
+                       $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
+               {
+                       # Wrong version
+                       return false;
+               }
+               return true;
+       }
+
+}
+
+?>
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
new file mode 100644 (file)
index 0000000..a04de96
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+
+class DjVuHandler extends ImageHandler {
+       function isEnabled() {
+               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
+               if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
+                       wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       function mustRender() { return true; }
+       function isMultiPage() { return true; }
+
+       function validateParam( $name, $value ) {
+               if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
+                       if ( $value <= 0 ) {
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       return false;
+               }
+       }
+
+       function makeParamString( $params ) {
+               $page = isset( $params['page'] ) ? $params['page'] : 1;
+               if ( !isset( $params['width'] ) ) {
+                       return false;
+               }
+               return "{$params['width']}px-page{$page}";
+       }
+
+       function parseParamString( $str ) {
+               $m = false;
+               if ( preg_match( '/^(\d+)px-page(\d+)$/', $str, $m ) ) {
+                       return array( 'width' => $m[1], 'page' => $m[2] );
+               } else {
+                       return false;
+               }
+       }
+
+       function getScriptParams( $params ) {
+               return array(
+                       'width' => $params['width'],
+                       'page' => $params['page'],
+               );
+       }
+
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               global $wgDjvuRenderer, $wgDjvuPostProcessor;
+
+               // Fetch XML and check it, to give a more informative error message than the one which 
+               // normaliseParams will inevitably give.
+               $xml = $image->getMetadata();
+               if ( !$xml ) {
+                       return new MediaTransformError( 'thumbnail_error', @$params['width'], @$params['height'], 
+                               wfMsg( 'djvu_no_xml' ) );
+               }
+               
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $width = $params['width'];
+               $height = $params['height'];
+               $srcPath = $image->getImagePath();
+               $page = $params['page'];
+               $pageCount = $this->pageCount( $image );
+               if ( $page > $this->pageCount( $image ) ) {
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
+               }
+               
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       return new ThumbnailImage( $dstUrl, $width, $height, $dstPath );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'thumbnail_dest_directory' ) );
+               }
+
+               # Use a subshell (brackets) to aggregate stderr from both pipeline commands 
+               # before redirecting it to the overall stdout. This works in both Linux and Windows XP.
+               $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size={$width}x{$height} " .
+                       wfEscapeShellArg( $srcPath );
+               if ( $wgDjvuPostProcessor ) {
+                       $cmd .= " | {$wgDjvuPostProcessor}";
+               }
+               $cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1';
+               wfProfileIn( 'ddjvu' );
+               wfDebug( __METHOD__.": $cmd\n" );
+               $err = wfShellExec( $cmd, $retval );
+               wfProfileOut( 'ddjvu' );
+
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
+                                       wfHostname(), $retval, trim($err), $cmd ) );
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
+               } else {
+                       return new ThumbnailImage( $dstUrl, $width, $height, $dstPath );
+               }
+       }
+
+       /**
+        * Cache an instance of DjVuImage in an Image object, return that instance
+        */
+       function getDjVuImage( $image, $path ) {
+               if ( !$image ) {
+                       $deja = new DjVuImage( $path );
+               } elseif ( !isset( $image->dejaImage ) ) {
+                       $deja = $image->dejaImage = new DjVuImage( $path );
+               } else {
+                       $deja = $image->dejaImage;
+               }
+               return $deja;
+       }
+
+       /**
+        * Cache a document tree for the DjVu XML metadata
+        */
+       function getMetaTree( $image ) {
+               if ( isset( $image->dejaMetaTree ) ) {
+                       return $image->dejaMetaTree;
+               }
+
+               $metadata = $image->getMetadata();
+               if ( !$this->isMetadataValid( $image, $metadata ) ) {
+                       wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
+                       return false;
+               }
+               wfProfileIn( __METHOD__ );
+
+               wfSuppressWarnings();
+               try {
+                       $image->dejaMetaTree = new SimpleXMLElement( $metadata );
+               } catch( Exception $e ) {
+                       wfDebug( "Bogus multipage XML metadata on '$image->name'\n" );
+                       // Set to false rather than null to avoid further attempts
+                       $image->dejaMetaTree = false;
+               }
+               wfRestoreWarnings();
+               wfProfileOut( __METHOD__ );
+               return $image->dejaMetaTree;
+       }
+
+       function getImageSize( $image, $path ) {
+               return $this->getDjVuImage( $image, $path )->getImageSize();
+       }
+
+       function getThumbType( $ext, $mime ) {
+               global $wgDjvuOutputExtension;
+               static $mime;
+               if ( !isset( $mime ) ) {
+                       $magic = MimeMagic::singleton();
+                       $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
+               }
+               return array( $wgDjvuOutputExtension, $mime );
+       }
+
+       function getMetadata( $image, $path ) {
+               wfDebug( "Getting DjVu metadata for $path\n" );
+               return $this->getDjVuImage( $image, $path )->retrieveMetaData();
+       }
+
+       function getMetadataType( $image ) {
+               return 'djvuxml';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               return !empty( $metadata ) && $metadata != serialize(array());
+       }
+
+       function pageCount( $image ) {
+               $tree = $this->getMetaTree( $image );
+               if ( !$tree ) {
+                       return false;
+               }
+               return count( $tree->xpath( '//OBJECT' ) );
+       }
+
+       function getPageDimensions( $image, $page ) {
+               $tree = $this->getMetaTree( $image );
+               if ( !$tree ) {
+                       return false;
+               }
+
+               $o = $tree->BODY[0]->OBJECT[$page-1];
+               if ( $o ) {
+                       return array(
+                               'width' => intval( $o['width'] ),
+                               'height' => intval( $o['height'] )
+                       );
+               } else {
+                       return false;
+               }
+       }
+}
+
+?>
diff --git a/includes/media/Generic.php b/includes/media/Generic.php
new file mode 100644 (file)
index 0000000..44c08d7
--- /dev/null
@@ -0,0 +1,292 @@
+<?php
+
+/**
+ * Media-handling base classes and generic functionality
+ */
+
+/**
+ * Base media handler class
+ */
+abstract class MediaHandler {
+       const TRANSFORM_LATER = 1;
+
+       /**
+        * Instance cache
+        */
+       static $handlers = array();
+
+       /**
+        * Get a MediaHandler for a given MIME type from the instance cache
+        */
+       static function getHandler( $type ) {
+               global $wgMediaHandlers;
+               if ( !isset( $wgMediaHandlers[$type] ) ) {
+                       return false;
+               }
+               $class = $wgMediaHandlers[$type];
+               if ( !isset( self::$handlers[$class] ) ) {
+                       self::$handlers[$class] = new $class;
+                       if ( !self::$handlers[$class]->isEnabled() ) {
+                               self::$handlers[$class] = false;
+                       }
+               }
+               return self::$handlers[$class];
+       }
+
+       /*
+        * Validate a thumbnail parameter at parse time. 
+        * Return true to accept the parameter, and false to reject it.
+        * If you return false, the parser will do something quiet and forgiving.
+        */
+       abstract function validateParam( $name, $value );
+
+       /**
+        * Merge a parameter array into a string appropriate for inclusion in filenames
+        */
+       abstract function makeParamString( $params );
+
+       /**
+        * Parse a param string made with makeParamString back into an array
+        */
+       abstract function parseParamString( $str );
+
+       /**
+        * Changes the parameter array as necessary, ready for transformation. 
+        * Should be idempotent.
+        * Returns false if the parameters are unacceptable and the transform should fail
+        */
+       abstract function normaliseParams( $image, &$params );
+
+       /**
+        * Get an image size array like that returned by getimagesize(), or false if it 
+        * can't be determined.
+        *
+        * @param Image $image The image object, or false if there isn't one
+        * @param string $fileName The filename
+        * @return array
+        */
+       abstract function getImageSize( $image, $path );
+
+       /**
+        * Get handler-specific metadata which will be saved in the img_metadata field.
+        *
+        * @param Image $image The image object, or false if there isn't one
+        * @param string $fileName The filename
+        * @return string
+        */
+       function getMetadata( $image, $path ) { return ''; }
+
+       /**
+        * Get a string describing the type of metadata, for display purposes.
+        */
+       function getMetadataType( $image ) { return false; }
+
+       /**
+        * Check if the metadata string is valid for this handler.
+        * If it returns false, Image will reload the metadata from the file and update the database
+        */
+       function isMetadataValid( $image, $metadata ) { return true; }
+
+       /**
+        * Get a MediaTransformOutput object representing the transformed output. Does not 
+        * actually do the transform.
+        *
+        * @param Image $image The image object
+        * @param string $dstPath Filesystem destination path
+        * @param string $dstUrl Destination URL to use in output HTML
+        * @param array $params Arbitrary set of parameters validated by $this->validateParam()
+        */
+       function getTransform( $image, $dstPath, $dstUrl, $params ) {
+               return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
+       }
+
+       /**
+        * Get a MediaTransformOutput object representing the transformed output. Does the 
+        * transform unless $flags contains self::TRANSFORM_LATER.
+        *
+        * @param Image $image The image object
+        * @param string $dstPath Filesystem destination path
+        * @param string $dstUrl Destination URL to use in output HTML
+        * @param array $params Arbitrary set of parameters validated by $this->validateParam()
+        * @param integer $flags A bitfield, may contain self::TRANSFORM_LATER
+        */
+       abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
+
+       /**
+        * Get the thumbnail extension and MIME type for a given source MIME type
+        * @return array thumbnail extension and MIME type
+        */
+       function getThumbType( $ext, $mime ) {
+               return array( $ext, $mime );
+       }       
+
+       /**
+        * True if the handled types can be transformed
+        */
+       function canRender() { return true; }
+       /**
+        * True if handled types cannot be displayed directly in a browser 
+        * but can be rendered
+        */
+       function mustRender() { return false; }
+       /**
+        * True if the type has multi-page capabilities
+        */
+       function isMultiPage() { return false; }
+       /**
+        * Page count for a multi-page document, false if unsupported or unknown
+        */
+       function pageCount() { return false; }
+       /**
+        * False if the handler is disabled for all files
+        */
+       function isEnabled() { return true; }
+
+       /**
+        * Get an associative array of page dimensions
+        * Currently "width" and "height" are understood, but this might be 
+        * expanded in the future.
+        * Returns false if unknown or if the document is not multi-page.
+        */
+       function getPageDimensions( $image, $page ) {
+               $gis = $this->getImageSize( $image, $image->getImagePath() );
+               return array(
+                       'width' => $gis[0],
+                       'height' => $gis[1]
+               );
+       }
+}
+
+/**
+ * Media handler abstract base class for images
+ */
+abstract class ImageHandler extends MediaHandler {
+       function validateParam( $name, $value ) {
+               if ( in_array( $name, array( 'width', 'height' ) ) ) {
+                       if ( $value <= 0 ) {
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       return false;
+               }
+       }
+
+       function makeParamString( $params ) {
+               if ( isset( $params['physicalWidth'] ) ) {
+                       $width = $params['physicalWidth'];
+               } else {
+                       $width = $params['width'];
+               }
+               $width = intval( $width );
+               return "{$width}px";
+       }
+
+       function parseParamString( $str ) {
+               $m = false;
+               if ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
+                       return array( 'width' => $m[1] );
+               } else {
+                       return false;
+               }
+       }
+
+       function getScriptParams( $params ) {
+               return array( 'width' => $params['width'] );
+       }
+
+       function normaliseParams( $image, &$params ) {
+               $mimeType = $image->getMimeType();
+
+               if ( !isset( $params['width'] ) ) {
+                       return false;
+               }
+               if ( !isset( $params['page'] ) ) {
+                       $params['page'] = 1;
+               }
+               $srcWidth = $image->getWidth( $params['page'] );
+               $srcHeight = $image->getHeight( $params['page'] );
+               if ( isset( $params['height'] ) && $params['height'] != -1 ) {
+                       if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) {
+                               $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
+                       }
+               }
+               $params['height'] = Image::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
+               if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Get a transform output object without actually doing the transform
+        */
+       function getTransform( $image, $dstPath, $dstUrl, $params ) {
+               return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
+       }
+       
+       /**
+        * Validate thumbnail parameters and fill in the correct height
+        *
+        * @param integer &$width Specified width (input/output)
+        * @param integer &$height Height (output only)
+        * @return false to indicate that an error should be returned to the user. 
+        */
+       function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
+               $width = intval( $width );
+
+               # Sanity check $width
+               if( $width <= 0) {
+                       wfDebug( __METHOD__.": Invalid destination width: $width\n" );
+                       return false;
+               }
+               if ( $srcWidth <= 0 ) {
+                       wfDebug( __METHOD__.": Invalid source width: $srcWidth\n" );
+                       return false;
+               }
+
+               $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
+               return true;
+       }
+
+       function getScriptedTransform( $image, $script, $params ) {
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               $url = $script . '&' . wfArrayToCGI( $this->getScriptParams( $params ) );
+               return new ThumbnailImage( $url, $params['width'], $params['height'] );
+       }
+
+       /**
+        * Check for zero-sized thumbnails. These can be generated when
+        * no disk space is available or some other error occurs
+        *
+        * @param $dstPath The location of the suspect file
+        * @param $retval Return value of some shell process, file will be deleted if this is non-zero
+        * @return true if removed, false otherwise
+        */
+       function removeBadFile( $dstPath, $retval = 0 ) {
+               $removed = false;
+               if( file_exists( $dstPath ) ) {
+                       $thumbstat = stat( $dstPath );
+                       if( $thumbstat['size'] == 0 || $retval != 0 ) {
+                               wfDebugLog( 'thumbnail',
+                                       sprintf( 'Removing bad %d-byte thumbnail "%s"',
+                                               $thumbstat['size'], $dstPath ) );
+                               unlink( $dstPath );
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       function getImageSize( $image, $path ) {
+               wfSuppressWarnings();
+               $gis = getimagesize( $path );
+               wfRestoreWarnings();
+               return $gis;
+       }
+}
+
+?>
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
new file mode 100644 (file)
index 0000000..407760d
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+class SvgHandler extends ImageHandler {
+       function isEnabled() {
+               global $wgSVGConverters, $wgSVGConverter;
+               if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+                       wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       function mustRender() {
+               return true;
+       }
+
+       function normaliseParams( $image, &$params ) {
+               global $wgSVGMaxSize;
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+
+               # Don't make an image bigger than wgMaxSVGSize
+               $params['physicalWidth'] = $params['width'];
+               $params['physicalHeight'] = $params['height'];
+               if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
+                       $srcWidth = $image->getWidth( $params['page'] );
+                       $srcHeight = $image->getHeight( $params['page'] );
+                       $params['physicalWidth'] = $wgSVGMaxSize;
+                       $params['physicalHeight'] = Image::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
+               }
+               return true;
+       }
+       
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
+               
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $clientWidth = $params['width'];
+               $clientHeight = $params['height'];
+               $physicalWidth = $params['physicalWidth'];
+               $physicalHeight = $params['physicalHeight'];
+               $srcWidth = $image->getWidth();
+               $srcHeight = $image->getHeight();
+               $srcPath = $image->getImagePath();
+
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, 
+                               wfMsg( 'thumbnail_dest_directory' ) );
+               }
+
+               $err = false;
+               if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+                       $cmd = str_replace(
+                               array( '$path/', '$width', '$height', '$input', '$output' ),
+                               array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
+                                          intval( $physicalWidth ),
+                                          intval( $physicalHeight ),
+                                          wfEscapeShellArg( $srcPath ),
+                                          wfEscapeShellArg( $dstPath ) ),
+                               $wgSVGConverters[$wgSVGConverter] ) . " 2>&1";
+                       wfProfileIn( 'rsvg' );
+                       wfDebug( __METHOD__.": $cmd\n" );
+                       $err = wfShellExec( $cmd, $retval );
+                       wfProfileOut( 'rsvg' );
+               }
+
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
+                                       wfHostname(), $retval, trim($err), $cmd ) );
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+               } else {
+                       return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight );
+               }
+       }
+
+       function getImageSize( $image, $path ) {
+               return wfGetSVGsize( $path );
+       }
+
+       function getThumbType( $ext, $mime ) {
+               return array( 'png', 'image/png' );
+       }
+}
+?>
index b2ae92e..a960f02 100644 (file)
@@ -19,7 +19,7 @@ image/x-portable-graymap image/x-portable-greymap     [BITMAP]
 image/x-bmp image/bmp application/x-bmp application/bmp        [BITMAP]
 image/x-photoshop image/psd image/x-psd image/photoshop        [BITMAP]
  
-image/svg image/svg+xml application/svg+xml application/svg    [DRAWING]
+image/svg+xml application/svg+xml application/svg image/svg    [DRAWING]
 application/postscript [DRAWING]
 application/x-latex    [DRAWING]
 application/x-tex      [DRAWING]
index e7be6c0..3f88b0f 100644 (file)
@@ -2139,6 +2139,10 @@ In the latter case you can also use a link, e.g. [[{{ns:Special}}:Export/{{Media
 'missingimage'         => '<b>Missing image</b><br /><i>$1</i>',
 'filemissing'          => 'File missing',
 'thumbnail_error'   => 'Error creating thumbnail: $1',
+'djvu_page_error'   => 'DjVu page out of range',
+'djvu_no_xml'       => 'Unable to fetch XML for DjVu file',
+'thumbnail_invalid_params' => 'Invalid thumbnail parameters',
+'thumbnail_dest_directory' => 'Unable to create destination directory',
 
 # Special:Import
 'import'       => 'Import pages',
@@ -2816,8 +2820,8 @@ Please confirm that really want to recreate this page.',
 * Nederlands|nl",
 
 # Multipage image navigation
-'imgmultipageprev' => '&larr; previous page',
-'imgmultipagenext' => 'next page &rarr;',
+'imgmultipageprev' => ' previous page',
+'imgmultipagenext' => 'next page ',
 'imgmultigo' => 'Go!',
 'imgmultigotopre' => 'Go to page',
 'imgmultigotopost' => '',
index cb91c5d..e39910c 100644 (file)
@@ -480,4 +480,15 @@ p.mw-ipb-conveniencelinks {
 #file img, .gallerybox .thumb img {
        background: url(images/Checker-16x16.png) repeat;
 }
-*/
\ No newline at end of file
+*/
+.MediaTransformError {
+       border: thin solid #777;
+       background-color: #ccc;
+       padding: 0.1em;
+}
+.MediaTransformError td {
+       text-align: center;
+       vertical-align: middle;
+       font-size: 90%;
+}
+
index bd68c37..bd7c773 100644 (file)
@@ -1626,3 +1626,13 @@ p.mw-ipb-conveniencelinks {
 .texvc { direction: ltr; unicode-bidi: embed; }
 /* Stop floats from intruding into edit area in previews */
 #toolbar, #wpTextbox1 { clear: both; }
+
+.MediaTransformError {
+       background-color: #ccc;
+       padding: 0.1em;
+}
+.MediaTransformError td {
+       text-align: center;
+       vertical-align: middle;
+       font-size: 90%;
+}
index 42bc549..c67468b 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -9,44 +9,49 @@ define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
 require_once( './includes/WebStart.php' );
 wfProfileIn( 'thumb.php' );
 wfProfileIn( 'thumb.php-start' );
-require_once( './includes/GlobalFunctions.php' );
-require_once( './includes/ImageFunctions.php' );
+require_once( "$IP/includes/GlobalFunctions.php" );
+require_once( "$IP/includes/ImageFunctions.php" );
 
 $wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png.
 
-require_once( './includes/Image.php' );
-require_once( './includes/StreamFile.php' );
+require_once( "$IP/includes/StreamFile.php" );
+require_once( "$IP/includes/AutoLoader.php" );
 
 // Get input parameters
-$fileName = isset( $_REQUEST['f'] ) ? $_REQUEST['f'] : '';
-$width = isset( $_REQUEST['w'] ) ? intval( $_REQUEST['w'] ) : 0;
-$page = isset( $_REQUEST['p'] ) ? intval( $_REQUEST['p'] ) : null;
-
 if ( get_magic_quotes_gpc() ) {
-       $fileName = stripslashes( $fileName );
+       $params = array_map( 'stripslashes', $_REQUEST );
+} else {
+       $params = $_REQUEST;
 }
 
-$pre_render= isset($_REQUEST['r']) && $_REQUEST['r']!="0";
+$fileName = isset( $params['f'] ) ? $params['f'] : '';
+unset( $params['f'] );
+
+// Backwards compatibility parameters
+if ( isset( $params['w'] ) ) {
+       $params['width'] = $params['w'];
+       unset( $params['w'] );
+}
+if ( isset( $params['p'] ) ) {
+       $params['page'] = $params['p'];
+}
+unset( $params['r'] );
 
 // Some basic input validation
 $fileName = strtr( $fileName, '\\/', '__' );
 
 // Work out paths, carefully avoiding constructing an Image object because that won't work yet
+$handler = thumbGetHandler( $fileName );
+if ( $handler ) {
+       $imagePath = wfImageDir( $fileName ) . '/' . $fileName;
+       $thumbName = $handler->makeParamString( $params ) . "-$fileName";
+       $thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
 
-$imagePath = wfImageDir( $fileName ) . '/' . $fileName;
-$thumbName = "{$width}px-$fileName";
-if ( ! is_null( $page ) ) {
-       $thumbName = 'page' . $page . '-' . $thumbName;
-}
-if ( $pre_render ) {
-       $thumbName .= '.png';
-}
-$thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
-
-if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
-       wfStreamFile( $thumbPath );
-       // Can't log profiling data with no Setup.php
-       exit;
+       if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
+               wfStreamFile( $thumbPath );
+               // Can't log profiling data with no Setup.php
+               exit;
+       }
 }
 
 // OK, no valid thumbnail, time to get out the heavy machinery
@@ -57,10 +62,7 @@ wfProfileIn( 'thumb.php-render' );
 $img = Image::newFromName( $fileName );
 try {
        if ( $img ) {
-               if ( ! is_null( $page ) ) {
-                       $img->selectPage( $page );
-               }
-               $thumb = $img->renderThumb( $width, false );
+               $thumb = $img->transform( $params, Image::RENDER_NOW );
        } else {
                $thumb = false;
        }
@@ -69,13 +71,33 @@ try {
        $thumb = false;
 }
 
-if ( $thumb && $thumb->path ) {
-       wfStreamFile( $thumb->path );
+if ( $thumb && $thumb->getPath() ) {
+       wfStreamFile( $thumb->getPath() );
+} elseif ( $img ) {
+       header( 'Cache-Control: no-cache' );
+       header( 'Content-Type: text/html; charset=utf-8' );
+       header( 'HTTP/1.1 500 Internal server error' );
+       if ( !$thumb ) {
+               $msg = wfMsgHtml( 'thumbnail_error', 'Image::transform() returned false' );
+       } elseif ( $thumb->isError() ) {
+               $msg = $thumb->toHtml();
+       } else {
+               $msg = wfMsgHtml( 'thumbnail_error', 'No path supplied in thumbnail object' );
+       }
+       echo <<<EOT
+<html><head><title>Error generating thumbnail</title></head>
+<body>
+$msg
+</body>
+</html>
+
+EOT;
 } else {
        $badtitle = wfMsg( 'badtitle' );
        $badtitletext = wfMsg( 'badtitletext' );
        header( 'Cache-Control: no-cache' );
        header( 'Content-Type: text/html; charset=utf-8' );
+       header( 'HTTP/1.1 500 Internal server error' );
        echo "<html><head>
        <title>$badtitle</title>
        <body>
@@ -89,4 +111,17 @@ wfProfileOut( 'thumb.php-render' );
 wfProfileOut( 'thumb.php' );
 wfLogProfilingData();
 
+//--------------------------------------------------------------------------
+
+function thumbGetHandler( $fileName ) {
+       // Determine type
+       $magic = MimeMagic::singleton();
+       $extPos = strrpos( $fileName, '.' );
+       if ( $extPos === false ) {
+               return false;
+       }
+       $mime = $magic->guessTypesForExtension( substr( $fileName, $extPos + 1 ) );
+       return MediaHandler::getHandler( $mime );
+}
+
 ?>