]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/imagefile.php
Merge commit 'refs/merge-requests/41' of https://gitorious.org/social/mainline into...
[quix0rs-gnu-social.git] / lib / imagefile.php
index 76231516f4e28f59e5a911cbd44ada183ff07998..2dc260b1bf8cad511abe131d254ab4648db659d8 100644 (file)
@@ -47,16 +47,18 @@ class ImageFile
 {
     var $id;
     var $filepath;
-    var $barename;
+    var $filename;
     var $type;
     var $height;
     var $width;
     var $rotate=0;  // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
+    var $animated = null;  // Animated image? (has more than 1 frame). null means untested
 
     function __construct($id=null, $filepath=null, $type=null, $width=null, $height=null)
     {
         $this->id = $id;
         $this->filepath = $filepath;
+        $this->filename = basename($filepath);
 
         $info = @getimagesize($this->filepath);
 
@@ -76,25 +78,31 @@ class ImageFile
         $this->width = ($info) ? $info[0]:$width;
         $this->height = ($info) ? $info[1]:$height;
 
-        // Orientation value to rotate thumbnails properly
-        $exif = exif_read_data($this->filepath);
-        if (isset($exif['Orientation'])) {
-            switch ((int)$exif['Orientation']) {
-            case 1: // top is top
-                $this->rotate = 0;
-                break;
-            case 3: // top is bottom
-                $this->rotate = 180;
-                break;
-            case 6: // top is right
-                $this->rotate = -90;
-                break;
-            case 8: // top is left
-                $this->rotate = 90;
-                break;
+        if ($this->type == IMAGETYPE_JPEG && function_exists('exif_read_data')) {
+            // Orientation value to rotate thumbnails properly
+            $exif = exif_read_data($this->filepath);
+            if (is_array($exif) && isset($exif['Orientation'])) {
+                switch ((int)$exif['Orientation']) {
+                case 1: // top is top
+                    $this->rotate = 0;
+                    break;
+                case 3: // top is bottom
+                    $this->rotate = 180;
+                    break;
+                case 6: // top is right
+                    $this->rotate = -90;
+                    break;
+                case 8: // top is left
+                    $this->rotate = 90;
+                    break;
+                }
+                // If we ever write this back, Orientation should be set to '1'
             }
-            // If we ever write this back, Orientation should be set to '1'
+        } elseif ($this->type === IMAGETYPE_GIF) {
+            $this->animated = $this->isAnimatedGif();
         }
+
+        Event::handle('FillImageFileMetadata', array($this));
     }
 
     public static function fromFileObject(File $file)
@@ -102,6 +110,9 @@ class ImageFile
         $imgPath = null;
         $media = common_get_mime_media($file->mimetype);
         if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
+            if (empty($file->filename)) {
+                throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.'));
+            }
             switch ($media) {
             case 'image':
                 $imgPath = $file->getPath();
@@ -191,79 +202,108 @@ class ImageFile
                                     $size,
                                     common_timestamp());
         $outpath = Avatar::path($outname);
-        $this->resizeTo($outpath, $size, $size, $x, $y, $w, $h);
+        $this->resizeTo($outpath, array('width'=>$size, 'height'=>$size,
+                                        'x'=>$x,        'y'=>$y,
+                                        'w'=>$w,        'h'=>$h));
         return $outname;
     }
 
     /**
      * Copy the image file to the given destination.
-     * For obscure formats, this will automatically convert to PNG;
-     * otherwise the original file will be copied as-is.
+     *
+     * This function may modify the resulting file. Please use the
+     * returned ImageFile object to read metadata (width, height etc.)
      *
      * @param string $outpath
-     * @return string filename
+     * @return ImageFile the image stored at target path
      */
     function copyTo($outpath)
     {
-        return $this->resizeTo($outpath, $this->width, $this->height);
+        return new ImageFile(null, $this->resizeTo($outpath));
     }
 
     /**
      * Create and save a thumbnail image.
      *
      * @param string $outpath
-     * @param int $width target width
-     * @param int $height target height
-     * @param int $x (default 0) upper-left corner to crop from
-     * @param int $y (default 0) upper-left corner to crop from
-     * @param int $w (default full) width of image area to crop
-     * @param int $h (default full) height of image area to crop
+     * @param array $box    width, height, boundary box (x,y,w,h) defaults to full image
      * @return string full local filesystem filename
      */
-    function resizeTo($outpath, $width, $height, $x=0, $y=0, $w=null, $h=null)
+    function resizeTo($outpath, array $box=array())
     {
-        $w = ($w === null) ? $this->width:$w;
-        $h = ($h === null) ? $this->height:$h;
-        $targetType = $this->preferredType();
+        $box['width'] = isset($box['width']) ? intval($box['width']) : $this->width;
+        $box['height'] = isset($box['height']) ? intval($box['height']) : $this->height;
+        $box['x'] = isset($box['x']) ? intval($box['x']) : 0;
+        $box['y'] = isset($box['y']) ? intval($box['y']) : 0;
+        $box['w'] = isset($box['w']) ? intval($box['w']) : $this->width;
+        $box['h'] = isset($box['h']) ? intval($box['h']) : $this->height;
 
         if (!file_exists($this->filepath)) {
             // TRANS: Exception thrown during resize when image has been registered as present, but is no longer there.
             throw new Exception(_('Lost our file.'));
         }
 
-        // Don't crop/scale if it isn't necessary
-        if ($width === $this->width
-            && $height === $this->height
-            && $x === 0
-            && $y === 0
-            && $w === $this->width
-            && $h === $this->height
-            && $this->type == $targetType) {
-
-            @copy($this->filepath, $outpath);
-            return $outpath;
+        // Don't rotate/crop/scale if it isn't necessary
+        if ($box['width'] === $this->width
+                && $box['height'] === $this->height
+                && $box['x'] === 0
+                && $box['y'] === 0
+                && $box['w'] === $this->width
+                && $box['h'] === $this->height
+                && $this->type == $this->preferredType()) {
+            if ($this->rotate == 0) {
+                // No rotational difference, just copy it as-is
+                @copy($this->filepath, $outpath);
+                return $outpath;
+            } elseif (abs($this->rotate) == 90) {
+                // Box is rotated 90 degrees in either direction,
+                // so we have to redefine x to y and vice versa.
+                $tmp = $box['width'];
+                $box['width'] = $box['height'];
+                $box['height'] = $tmp;
+                $tmp = $box['x'];
+                $box['x'] = $box['y'];
+                $box['y'] = $tmp;
+                $tmp = $box['w'];
+                $box['w'] = $box['h'];
+                $box['h'] = $tmp;
+            }
+        }
+
+
+        if (Event::handle('StartResizeImageFile', array($this, $outpath, $box))) {
+            $this->resizeToFile($outpath, $box);
+        }
+
+        if (!file_exists($outpath)) {
+            throw new UseFileAsThumbnailException($this->id);
         }
 
+        return $outpath;
+    }
+
+    protected function resizeToFile($outpath, array $box)
+    {
         switch ($this->type) {
-         case IMAGETYPE_GIF:
+        case IMAGETYPE_GIF:
             $image_src = imagecreatefromgif($this->filepath);
             break;
-         case IMAGETYPE_JPEG:
+        case IMAGETYPE_JPEG:
             $image_src = imagecreatefromjpeg($this->filepath);
             break;
-         case IMAGETYPE_PNG:
+        case IMAGETYPE_PNG:
             $image_src = imagecreatefrompng($this->filepath);
             break;
-         case IMAGETYPE_BMP:
+        case IMAGETYPE_BMP:
             $image_src = imagecreatefrombmp($this->filepath);
             break;
-         case IMAGETYPE_WBMP:
+        case IMAGETYPE_WBMP:
             $image_src = imagecreatefromwbmp($this->filepath);
             break;
-         case IMAGETYPE_XBM:
+        case IMAGETYPE_XBM:
             $image_src = imagecreatefromxbm($this->filepath);
             break;
-         default:
+        default:
             // TRANS: Exception thrown when trying to resize an unknown file type.
             throw new Exception(_('Unknown file type'));
         }
@@ -272,7 +312,7 @@ class ImageFile
             $image_src = imagerotate($image_src, $this->rotate, 0);
         }
 
-        $image_dest = imagecreatetruecolor($width, $height);
+        $image_dest = imagecreatetruecolor($box['width'], $box['height']);
 
         if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
 
@@ -295,9 +335,9 @@ class ImageFile
             }
         }
 
-        imagecopyresampled($image_dest, $image_src, 0, 0, $x, $y, $width, $height, $w, $h);
+        imagecopyresampled($image_dest, $image_src, 0, 0, $box['x'], $box['y'], $box['width'], $box['height'], $box['w'], $box['h']);
 
-        switch ($targetType) {
+        switch ($this->preferredType()) {
          case IMAGETYPE_GIF:
             imagegif($image_dest, $outpath);
             break;
@@ -314,10 +354,9 @@ class ImageFile
 
         imagedestroy($image_src);
         imagedestroy($image_dest);
-
-        return $outpath;
     }
 
+
     /**
      * Several obscure file types should be normalized to PNG on resize.
      *
@@ -466,6 +505,41 @@ class ImageFile
                     is_null($cw) ? $width : intval($cw),
                     is_null($ch) ? $height : intval($ch));
     }
+
+    /**
+     * Animated GIF test, courtesy of frank at huddler dot com et al:
+     * http://php.net/manual/en/function.imagecreatefromgif.php#104473
+     * Modified so avoid landing inside of a header (and thus not matching our regexp).
+     */
+    protected function isAnimatedGif()
+    {
+        if (!($fh = @fopen($this->filepath, 'rb'))) {
+            return false;
+        }
+
+        $count = 0;
+        //an animated gif contains multiple "frames", with each frame having a
+        //header made up of:
+        // * a static 4-byte sequence (\x00\x21\xF9\x04)
+        // * 4 variable bytes
+        // * a static 2-byte sequence (\x00\x2C)
+        // In total the header is maximum 10 bytes.
+
+        // We read through the file til we reach the end of the file, or we've found
+        // at least 2 frame headers
+        while(!feof($fh) && $count < 2) {
+            $chunk = fread($fh, 1024 * 100); //read 100kb at a time
+            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00\x2C#s', $chunk, $matches);
+            // rewind in case we ended up in the middle of the header, but avoid
+            // infinite loop (i.e. don't rewind if we're already in the end).
+            if (!feof($fh) && ftell($fh) >= 9) {
+                fseek($fh, -9, SEEK_CUR);
+            }
+        }
+
+        fclose($fh);
+        return $count > 1;
+    }
 }
 
 //PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one