imagefile.php 27 KB
Newer Older
1 2
<?php
/**
3
 * StatusNet, the distributed open-source microblogging tool
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * Abstraction for an image file
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Image
23
 * @package   StatusNet
24 25
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
26
 * @copyright 2008-2009 StatusNet, Inc.
27
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28
 * @link      http://status.net/
29 30
 */

31
if (!defined('GNUSOCIAL')) { exit(1); }
32 33 34 35 36 37 38

/**
 * A wrapper on uploaded files
 *
 * Makes it slightly easier to accept an image file from upload.
 *
 * @category Image
39
 * @package  StatusNet
40 41
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
42
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
43
 * @link     http://status.net/
44 45 46 47
 */

class ImageFile
{
48 49
    var $id;
    var $filepath;
50
    var $filename;
51 52 53
    var $type;
    var $height;
    var $width;
54
    var $rotate=0;  // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
55
    var $animated = null;  // Animated image? (has more than 1 frame). null means untested
mmn's avatar
mmn committed
56
    var $mimetype = null;   // The _ImageFile_ mimetype, _not_ the originating File object
57

58 59
    protected $fileRecord = null;

60
    function __construct($id, $filepath)
61
    {
62
        $this->id = $id;
63
        if (!empty($this->id)) {
64 65
            $this->fileRecord = new File();
            $this->fileRecord->id = $this->id;
mmn's avatar
mmn committed
66
            if (!$this->fileRecord->find(true)) {
67 68
                // If we have set an ID, we need that ID to exist!
                throw new NoResultException($this->fileRecord);
69 70
            }
        }
71 72 73

        // These do not have to be the same as fileRecord->filename for example,
        // since we may have generated an image source file from something else!
74
        $this->filepath = $filepath;
75
        $this->filename = basename($filepath);
76

77
        $info = @getimagesize($this->filepath);
78 79 80 81 82 83 84 85 86

        if (!(
            ($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
            ($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
            $info[2] == IMAGETYPE_BMP ||
            ($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
            ($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
            ($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {

87
            // TRANS: Exception thrown when trying to upload an unsupported image file format.
88
            throw new UnsupportedMediaException(_('Unsupported image format.'), $this->filepath);
89 90
        }

91 92 93
        $this->width    = $info[0];
        $this->height   = $info[1];
        $this->type     = $info[2];
mmn's avatar
mmn committed
94
        $this->mimetype = $info['mime'];
95

mmn's avatar
mmn committed
96
        if ($this->type === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
97
            // Orientation value to rotate thumbnails properly
98
            $exif = @exif_read_data($this->filepath);
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
            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'
115
            }
116 117
        } elseif ($this->type === IMAGETYPE_GIF) {
            $this->animated = $this->isAnimatedGif();
118
        }
119 120

        Event::handle('FillImageFileMetadata', array($this));
121 122
    }

123 124 125 126 127
    public static function fromFileObject(File $file)
    {
        $imgPath = null;
        $media = common_get_mime_media($file->mimetype);
        if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
mmn's avatar
mmn committed
128
            if (empty($file->filename) && !file_exists($imgPath)) {
129 130
                throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.'));
            }
131 132 133 134

            // First some mimetype specific exceptions
            switch ($file->mimetype) {
            case 'image/svg+xml':
135
                throw new UseFileAsThumbnailException($file);
136 137 138
            }

            // And we'll only consider it an image if it has such a media type
139 140 141 142 143 144 145 146 147 148 149 150 151 152
            switch ($media) {
            case 'image':
                $imgPath = $file->getPath();
                break;
            default:
                throw new UnsupportedMediaException(_('Unsupported media format.'), $file->getPath());
            }
        }

        if (!file_exists($imgPath)) {
            throw new ServerException(sprintf('Image not available locally: %s', $imgPath));
        }

        try {
153
            $image = new ImageFile($file->getID(), $imgPath);
154 155
        } catch (UnsupportedMediaException $e) {
            // Avoid deleting the original
156 157 158 159 160 161 162 163
            try {
                if ($imgPath !== $file->getPath()) {
                    @unlink($imgPath);
                }
            } catch (FileNotFoundException $e) {
                // File reported (via getPath) that the original file
                // doesn't exist anyway, so it's safe to delete $imgPath
                @unlink($imgPath);
164 165 166 167 168 169
            }
            throw $e;
        }
        return $image;
    }

170 171 172
    public function getPath()
    {
        if (!file_exists($this->filepath)) {
173
            throw new FileNotFoundException($this->filepath);
174 175 176 177 178
        }

        return $this->filepath;
    }

179 180 181
    static function fromUpload($param='upload')
    {
        switch ($_FILES[$param]['error']) {
Evan Prodromou's avatar
Evan Prodromou committed
182
         case UPLOAD_ERR_OK: // success, jump out
183
            break;
184

Evan Prodromou's avatar
Evan Prodromou committed
185 186
         case UPLOAD_ERR_INI_SIZE:
         case UPLOAD_ERR_FORM_SIZE:
187 188
            // TRANS: Exception thrown when too large a file is uploaded.
            // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
189 190
            throw new Exception(sprintf(_('That file is too big. The maximum file size is %s.'), ImageFile::maxFileSize()));

Evan Prodromou's avatar
Evan Prodromou committed
191
         case UPLOAD_ERR_PARTIAL:
192
            @unlink($_FILES[$param]['tmp_name']);
193
            // TRANS: Exception thrown when uploading an image and that action could not be completed.
194
            throw new Exception(_('Partial upload.'));
195

196 197
         case UPLOAD_ERR_NO_FILE:
            // No file; probably just a non-AJAX submission.
198 199
            throw new ClientException(_('No file uploaded.'));

Evan Prodromou's avatar
Evan Prodromou committed
200
         default:
201
            common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']);
202
            // TRANS: Exception thrown when uploading an image fails for an unknown reason.
203 204
            throw new Exception(_('System error uploading file.'));
        }
205

206
        $info = @getimagesize($_FILES[$param]['tmp_name']);
207

208
        if (!$info) {
209
            @unlink($_FILES[$param]['tmp_name']);
210
            // TRANS: Exception thrown when uploading a file as image that is not an image or is a corrupt file.
211
            throw new UnsupportedMediaException(_('Not an image or corrupt file.'), '[deleted]');
212
        }
213

214 215
        return new ImageFile(null, $_FILES[$param]['tmp_name']);
    }
216

217 218
    /**
     * Copy the image file to the given destination.
219 220 221
     *
     * This function may modify the resulting file. Please use the
     * returned ImageFile object to read metadata (width, height etc.)
222 223
     *
     * @param string $outpath
224
     * @return ImageFile the image stored at target path
225 226 227
     */
    function copyTo($outpath)
    {
228
        return new ImageFile(null, $this->resizeTo($outpath));
229 230
    }

231 232 233 234
    /**
     * Create and save a thumbnail image.
     *
     * @param string $outpath
235
     * @param array $box    width, height, boundary box (x,y,w,h) defaults to full image
236 237
     * @return string full local filesystem filename
     */
238
    function resizeTo($outpath, array $box=array())
239
    {
240 241 242 243 244 245 246
        $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;

247
        if (!file_exists($this->filepath)) {
248
            // TRANS: Exception thrown during resize when image has been registered as present, but is no longer there.
249 250
            throw new Exception(_('Lost our file.'));
        }
Evan Prodromou's avatar
Evan Prodromou committed
251

252
        // Don't rotate/crop/scale if it isn't necessary
253
        if ($box['width'] === $this->width
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
                && $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;
            }
277
        }
278

279

280 281 282 283
        if (Event::handle('StartResizeImageFile', array($this, $outpath, $box))) {
            $this->resizeToFile($outpath, $box);
        }

284
        if (!file_exists($outpath)) {
285 286 287 288 289
            if ($this->fileRecord instanceof File) {
                throw new UseFileAsThumbnailException($this->fileRecord);
            } else {
                throw new UnsupportedMediaException('No local File object exists for ImageFile.');
            }
290 291
        }

292 293 294 295 296
        return $outpath;
    }

    protected function resizeToFile($outpath, array $box)
    {
297
        switch ($this->type) {
298
        case IMAGETYPE_GIF:
299
            $image_src = imagecreatefromgif($this->filepath);
300
            break;
301
        case IMAGETYPE_JPEG:
302 303
            $image_src = imagecreatefromjpeg($this->filepath);
            break;
304
        case IMAGETYPE_PNG:
305 306
            $image_src = imagecreatefrompng($this->filepath);
            break;
307
        case IMAGETYPE_BMP:
308 309
            $image_src = imagecreatefrombmp($this->filepath);
            break;
310
        case IMAGETYPE_WBMP:
311 312
            $image_src = imagecreatefromwbmp($this->filepath);
            break;
313
        case IMAGETYPE_XBM:
314 315
            $image_src = imagecreatefromxbm($this->filepath);
            break;
316
        default:
317
            // TRANS: Exception thrown when trying to resize an unknown file type.
318 319 320
            throw new Exception(_('Unknown file type'));
        }

321 322 323 324
        if ($this->rotate != 0) {
            $image_src = imagerotate($image_src, $this->rotate, 0);
        }

325
        $image_dest = imagecreatetruecolor($box['width'], $box['height']);
326

327
        if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
328 329

            $transparent_idx = imagecolortransparent($image_src);
330

331
            if ($transparent_idx >= 0) {
332

333 334 335 336
                $transparent_color = imagecolorsforindex($image_src, $transparent_idx);
                $transparent_idx = imagecolorallocate($image_dest, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
                imagefill($image_dest, 0, 0, $transparent_idx);
                imagecolortransparent($image_dest, $transparent_idx);
337

338
            } elseif ($this->type == IMAGETYPE_PNG) {
339

340 341 342 343
                imagealphablending($image_dest, false);
                $transparent = imagecolorallocatealpha($image_dest, 0, 0, 0, 127);
                imagefill($image_dest, 0, 0, $transparent);
                imagesavealpha($image_dest, true);
344

345 346 347
            }
        }

348
        imagecopyresampled($image_dest, $image_src, 0, 0, $box['x'], $box['y'], $box['width'], $box['height'], $box['w'], $box['h']);
349

350
        switch ($this->preferredType()) {
351 352 353 354
         case IMAGETYPE_GIF:
            imagegif($image_dest, $outpath);
            break;
         case IMAGETYPE_JPEG:
355
            imagejpeg($image_dest, $outpath, common_config('image', 'jpegquality'));
356 357 358 359 360
            break;
         case IMAGETYPE_PNG:
            imagepng($image_dest, $outpath);
            break;
         default:
361
            // TRANS: Exception thrown when trying resize an unknown file type.
362
            throw new Exception(_('Unknown file type'));
363
        }
Evan Prodromou's avatar
Evan Prodromou committed
364

365 366
        imagedestroy($image_src);
        imagedestroy($image_dest);
367 368
    }

369

370 371 372
    /**
     * Several obscure file types should be normalized to PNG on resize.
     *
373
     * @fixme consider flattening anything not GIF or JPEG to PNG
374 375
     * @return int
     */
376
    function preferredType()
377
    {
378
        if($this->type == IMAGETYPE_BMP) {
379 380 381
            //we don't want to save BMP... it's an inefficient, rare, antiquated format
            //save png instead
            return IMAGETYPE_PNG;
382
        } else if($this->type == IMAGETYPE_WBMP) {
383 384 385
            //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
            //save png instead
            return IMAGETYPE_PNG;
386
        } else if($this->type == IMAGETYPE_XBM) {
387 388 389 390
            //we don't want to save XBM... it's a rare format that we can't guarantee clients will support
            //save png instead
            return IMAGETYPE_PNG;
        }
391
        return $this->type;
392 393 394 395
    }

    function unlink()
    {
396
        @unlink($this->filepath);
397
    }
398 399

    static function maxFileSize()
400
    {
401 402 403
        $value = ImageFile::maxFileSizeInt();

        if ($value > 1024 * 1024) {
404 405 406
            $value = $value/(1024*1024);
            // TRANS: Number of megabytes. %d is the number.
            return sprintf(_m('%dMB','%dMB',$value),$value);
407
        } else if ($value > 1024) {
408 409 410
            $value = $value/1024;
            // TRANS: Number of kilobytes. %d is the number.
            return sprintf(_m('%dkB','%dkB',$value),$value);
411
        } else {
412 413
            // TRANS: Number of bytes. %d is the number.
            return sprintf(_m('%dB','%dB',$value),$value);
414
        }
415
    }
416 417 418 419 420 421 422 423

    static function maxFileSizeInt()
    {
        return min(ImageFile::strToInt(ini_get('post_max_size')),
                   ImageFile::strToInt(ini_get('upload_max_filesize')),
                   ImageFile::strToInt(ini_get('memory_limit')));
    }

424 425 426 427
    static function strToInt($str)
    {
        $unit = substr($str, -1);
        $num = substr($str, 0, -1);
428

429
        switch(strtoupper($unit)){
Evan Prodromou's avatar
Evan Prodromou committed
430 431 432 433 434 435
         case 'G':
            $num *= 1024;
         case 'M':
            $num *= 1024;
         case 'K':
            $num *= 1024;
436
        }
437

438 439
        return $num;
    }
440 441 442 443

    public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
    {
        return self::getScalingValues($this->width, $this->height,
444
                                        $maxWidth, $maxHeight, $crop, $this->rotate);
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
    }

    /*
     * Gets scaling values for images of various types. Cropping can be enabled.
     *
     * Values will scale _up_ to fit max values if cropping is enabled!
     * With cropping disabled, the max value of each axis will be respected.
     *
     * @param $width    int Original width
     * @param $height   int Original height
     * @param $maxW     int Resulting max width
     * @param $maxH     int Resulting max height
     * @param $crop     int Crop to the size (not preserving aspect ratio)
     */
    public static function getScalingValues($width, $height,
                                        $maxW=null, $maxH=null,
461
                                        $crop=null, $rotate=0)
462 463 464 465 466 467 468 469 470 471 472
    {
        $maxW = $maxW ?: common_config('thumbnail', 'width');
        $maxH = $maxH ?: common_config('thumbnail', 'height');
  
        if ($maxW < 1 || ($maxH !== null && $maxH < 1)) {
            throw new ServerException('Bad parameters for ImageFile::getScalingValues');
        } elseif ($maxH === null) {
            // if maxH is null, we set maxH to equal maxW and enable crop
            $maxH = $maxW;
            $crop = true;
        }
473 474 475 476 477 478 479

        // Because GD doesn't understand EXIF orientation etc.
        if (abs($rotate) == 90) {
            $tmp = $width;
            $width = $height;
            $height = $tmp;
        }
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
  
        // Cropping data (for original image size). Default values, 0 and null,
        // imply no cropping and with preserved aspect ratio (per axis).
        $cx = 0;    // crop x
        $cy = 0;    // crop y
        $cw = null; // crop area width
        $ch = null; // crop area height
  
        if ($crop) {
            $s_ar = $width / $height;
            $t_ar = $maxW / $maxH;

            $rw = $maxW;
            $rh = $maxH;

            // Source aspect ratio differs from target, recalculate crop points!
            if ($s_ar > $t_ar) {
                $cx = floor($width / 2 - $height * $t_ar / 2);
                $cw = ceil($height * $t_ar);
            } elseif ($s_ar < $t_ar) {
                $cy = floor($height / 2 - $width / $t_ar / 2);
                $ch = ceil($width / $t_ar);
            }
        } else {
            $rw = $maxW;
            $rh = ceil($height * $rw / $width);

            // Scaling caused too large height, decrease to max accepted value
            if ($rh > $maxH) {
                $rh = $maxH;
                $rw = ceil($width * $rh / $height);
            }
        }
        return array(intval($rw), intval($rh),
                    intval($cx), intval($cy),
515 516
                    is_null($cw) ? $width : intval($cw),
                    is_null($ch) ? $height : intval($ch));
517
    }
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550

    /**
     * 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);
551
        return $count >= 1; // number of animated frames apart from the original image
552
    }
553

554
    public function getFileThumbnail($width, $height, $crop, $upscale=false)
555 556 557 558 559 560 561 562 563 564 565
    {
        if (!$this->fileRecord instanceof File) {
            throw new ServerException('No File object attached to this ImageFile object.');
        }

        if ($width === null) {
            $width = common_config('thumbnail', 'width');
            $height = common_config('thumbnail', 'height');
            $crop = common_config('thumbnail', 'crop');
        }

566 567 568 569 570 571 572 573 574
        if (!$upscale) {
            if ($width > $this->width) {
                $width = $this->width;
            }
            if (!is_null($height) && $height > $this->height) {
                $height = $this->height;
            }
        }

575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
        if ($height === null) {
            $height = $width;
            $crop = true;
        }

        // Get proper aspect ratio width and height before lookup
        // We have to do it through an ImageFile object because of orientation etc.
        // Only other solution would've been to rotate + rewrite uploaded files
        // which we don't want to do because we like original, untouched data!
        list($width, $height, $x, $y, $w, $h) = $this->scaleToFit($width, $height, $crop);

        $thumb = File_thumbnail::pkeyGet(array(
                                            'file_id'=> $this->fileRecord->id,
                                            'width'  => $width,
                                            'height' => $height,
                                        ));
        if ($thumb instanceof File_thumbnail) {
            return $thumb;
        }

        $filename = $this->fileRecord->filehash ?: $this->filename;    // Remote files don't have $this->filehash
        $extension = File::guessMimeExtension($this->mimetype);
        $outname = "thumb-{$this->fileRecord->id}-{$width}x{$height}-{$filename}." . $extension;
        $outpath = File_thumbnail::path($outname);

        // The boundary box for our resizing
        $box = array('width'=>$width, 'height'=>$height,
                     'x'=>$x,         'y'=>$y,
                     'w'=>$w,         'h'=>$h);

        // Doublecheck that parameters are sane and integers.
        if ($box['width'] < 1 || $box['width'] > common_config('thumbnail', 'maxsize')
                || $box['height'] < 1 || $box['height'] > common_config('thumbnail', 'maxsize')
                || $box['w'] < 1 || $box['x'] >= $this->width
                || $box['h'] < 1 || $box['y'] >= $this->height) {
            // Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit
            common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box,true));
            throw new ServerException('Bad thumbnail size parameters.');
        }

        common_debug(sprintf('Generating a thumbnail of File id==%u of size %ux%u', $this->fileRecord->id, $width, $height));

        // Perform resize and store into file
        $this->resizeTo($outpath, $box);

620 621 622 623 624 625 626
        try {
            // Avoid deleting the original
            if (!in_array($this->getPath(), [File::path($this->filename), File_thumbnail::path($this->filename)])) {
                $this->unlink();
            }
        } catch (FileNotFoundException $e) {
            // $this->getPath() says the file doesn't exist anyway, so no point in trying to delete it!
627
        }
628

629 630
        return File_thumbnail::saveThumbnail($this->fileRecord->getID(),
                                      null, // no url since we generated it ourselves and can dynamically generate the url
631 632 633
                                      $width, $height,
                                      $outname);
    }
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
}

//PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one
if(!function_exists('imagecreatefrombmp')){
    //taken shamelessly from http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
    function imagecreatefrombmp($p_sFile)
    {
        //    Load the image into a string
        $file    =    fopen($p_sFile,"rb");
        $read    =    fread($file,10);
        while(!feof($file)&&($read<>""))
            $read    .=    fread($file,1024);

        $temp    =    unpack("H*",$read);
        $hex    =    $temp[1];
        $header    =    substr($hex,0,108);

        //    Process the header
        //    Structure: http://www.fastgraph.com/help/bmp_header_format.html
        if (substr($header,0,4)=="424d")
        {
            //    Cut it in parts of 2 bytes
            $header_parts    =    str_split($header,2);

            //    Get the width        4 bytes
            $width            =    hexdec($header_parts[19].$header_parts[18]);

            //    Get the height        4 bytes
            $height            =    hexdec($header_parts[23].$header_parts[22]);

            //    Unset the header params
            unset($header_parts);
        }

        //    Define starting X and Y
        $x                =    0;
        $y                =    1;

        //    Create newimage
        $image            =    imagecreatetruecolor($width,$height);

        //    Grab the body from the image
        $body            =    substr($hex,108);

        //    Calculate if padding at the end-line is needed
        //    Divided by two to keep overview.
        //    1 byte = 2 HEX-chars
        $body_size        =    (strlen($body)/2);
        $header_size    =    ($width*$height);

        //    Use end-line padding? Only when needed
        $usePadding        =    ($body_size>($header_size*3)+4);

        //    Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
        //    Calculate the next DWORD-position in the body
        for ($i=0;$i<$body_size;$i+=3)
        {
            //    Calculate line-ending and padding
            if ($x>=$width)
            {
                //    If padding needed, ignore image-padding
                //    Shift i to the ending of the current 32-bit-block
                if ($usePadding)
                    $i    +=    $width%4;

                //    Reset horizontal position
                $x    =    0;

                //    Raise the height-position (bottom-up)
                $y++;

                //    Reached the image-height? Break the for-loop
                if ($y>$height)
                    break;
            }

            //    Calculation of the RGB-pixel (defined as BGR in image-data)
            //    Define $i_pos as absolute position in the body
            $i_pos    =    $i*2;
            $r        =    hexdec($body[$i_pos+4].$body[$i_pos+5]);
            $g        =    hexdec($body[$i_pos+2].$body[$i_pos+3]);
            $b        =    hexdec($body[$i_pos].$body[$i_pos+1]);

            //    Calculate and draw the pixel
            $color    =    imagecolorallocate($image,$r,$g,$b);
            imagesetpixel($image,$x,$height-$y,$color);

            //    Raise the horizontal position
            $x++;
        }

        //    Unset the body / free the memory
        unset($body);

        //    Return image-object
        return $image;
    }
731
}   // if(!function_exists('imagecreatefrombmp'))