Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/lib/ -> gdlib.php (source)

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * gdlib.php - Collection of routines in Moodle related to
  20   * processing images using GD
  21   *
  22   * @package   core
  23   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Copies a rectangular portion of the source image to another rectangle in the destination image
  31   *
  32   * This function calls imagecopyresampled() if it is available and GD version is 2 at least.
  33   * Otherwise it reimplements the same behaviour. See the PHP manual page for more info.
  34   *
  35   * @link http://php.net/manual/en/function.imagecopyresampled.php
  36   * @param resource $dst_img the destination GD image resource
  37   * @param resource $src_img the source GD image resource
  38   * @param int $dst_x vthe X coordinate of the upper left corner in the destination image
  39   * @param int $dst_y the Y coordinate of the upper left corner in the destination image
  40   * @param int $src_x the X coordinate of the upper left corner in the source image
  41   * @param int $src_y the Y coordinate of the upper left corner in the source image
  42   * @param int $dst_w the width of the destination rectangle
  43   * @param int $dst_h the height of the destination rectangle
  44   * @param int $src_w the width of the source rectangle
  45   * @param int $src_h the height of the source rectangle
  46   * @return bool tru on success, false otherwise
  47   */
  48  function imagecopybicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) {
  49      global $CFG;
  50  
  51      if (function_exists('imagecopyresampled')) {
  52         return imagecopyresampled($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y,
  53                                   $dst_w, $dst_h, $src_w, $src_h);
  54      }
  55  
  56      $totalcolors = imagecolorstotal($src_img);
  57      for ($i=0; $i<$totalcolors; $i++) {
  58          if ($colors = imagecolorsforindex($src_img, $i)) {
  59              imagecolorallocate($dst_img, $colors['red'], $colors['green'], $colors['blue']);
  60          }
  61      }
  62  
  63      $scalex = ($src_w - 1) / $dst_w;
  64      $scaley = ($src_h - 1) / $dst_h;
  65  
  66      $scalex2 = $scalex / 2.0;
  67      $scaley2 = $scaley / 2.0;
  68  
  69      for ($j = 0; $j < $dst_h; $j++) {
  70          $sy = $j * $scaley;
  71  
  72          for ($i = 0; $i < $dst_w; $i++) {
  73              $sx = $i * $scalex;
  74  
  75              $c1 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx, (int)$sy + $scaley2));
  76              $c2 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx, (int)$sy));
  77              $c3 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx + $scalex2, (int)$sy + $scaley2));
  78              $c4 = imagecolorsforindex($src_img, imagecolorat($src_img, (int)$sx + $scalex2, (int)$sy));
  79  
  80              $red = (int) (($c1['red'] + $c2['red'] + $c3['red'] + $c4['red']) / 4);
  81              $green = (int) (($c1['green'] + $c2['green'] + $c3['green'] + $c4['green']) / 4);
  82              $blue = (int) (($c1['blue'] + $c2['blue'] + $c3['blue'] + $c4['blue']) / 4);
  83  
  84              $color = imagecolorclosest($dst_img, $red, $green, $blue);
  85              imagesetpixel($dst_img, $i + $dst_x, $j + $dst_y, $color);
  86          }
  87      }
  88  }
  89  
  90  /**
  91   * Stores optimised icon images in icon file area.
  92   *
  93   * Since 2.9 this function will generate an icon in the same format as the original file when possible.
  94   * To counter that behaviour, you can use the argument $preferpng to generate a PNG icon.
  95   *
  96   * @param context $context
  97   * @param string $component
  98   * @param string filearea
  99   * @param int $itemid
 100   * @param string $originalfile
 101   * @param boolean $preferpng When true, it will try to generate a PNG file regardless of the original file.
 102   * @return mixed new unique revision number or false if not saved
 103   */
 104  function process_new_icon($context, $component, $filearea, $itemid, $originalfile, $preferpng = false) {
 105      global $CFG;
 106  
 107      if (!is_file($originalfile)) {
 108          return false;
 109      }
 110  
 111      $imageinfo = getimagesize($originalfile);
 112      $imagefnc = '';
 113  
 114      if (empty($imageinfo)) {
 115          return false;
 116      }
 117  
 118      $image = new stdClass();
 119      $image->width  = $imageinfo[0];
 120      $image->height = $imageinfo[1];
 121      $image->type   = $imageinfo[2];
 122  
 123      $t = null;
 124      switch ($image->type) {
 125          case IMAGETYPE_GIF:
 126              if (function_exists('imagecreatefromgif')) {
 127                  $im = imagecreatefromgif($originalfile);
 128              } else {
 129                  debugging('GIF not supported on this server');
 130                  return false;
 131              }
 132              // Guess transparent colour from GIF.
 133              $transparent = imagecolortransparent($im);
 134              if ($transparent != -1) {
 135                  $t = imagecolorsforindex($im, $transparent);
 136              }
 137              break;
 138          case IMAGETYPE_JPEG:
 139              if (function_exists('imagecreatefromjpeg')) {
 140                  $im = imagecreatefromjpeg($originalfile);
 141              } else {
 142                  debugging('JPEG not supported on this server');
 143                  return false;
 144              }
 145              // If the user uploads a jpeg them we should process as a jpeg if possible.
 146              if (!$preferpng && function_exists('imagejpeg')) {
 147                  $imagefnc = 'imagejpeg';
 148                  $imageext = '.jpg';
 149                  $filters = null; // Not used.
 150                  $quality = 90;
 151              }
 152              break;
 153          case IMAGETYPE_PNG:
 154              if (function_exists('imagecreatefrompng')) {
 155                  $im = imagecreatefrompng($originalfile);
 156              } else {
 157                  debugging('PNG not supported on this server');
 158                  return false;
 159              }
 160              break;
 161          default:
 162              return false;
 163      }
 164  
 165      // The conversion has not been decided yet, let's apply defaults (png with fallback to jpg).
 166      if (empty($imagefnc)) {
 167          if (function_exists('imagepng')) {
 168              $imagefnc = 'imagepng';
 169              $imageext = '.png';
 170              $filters = PNG_NO_FILTER;
 171              $quality = 1;
 172          } else if (function_exists('imagejpeg')) {
 173              $imagefnc = 'imagejpeg';
 174              $imageext = '.jpg';
 175              $filters = null; // Not used.
 176              $quality = 90;
 177          } else {
 178              debugging('Jpeg and png not supported on this server, please fix server configuration');
 179              return false;
 180          }
 181      }
 182  
 183      if (function_exists('imagecreatetruecolor')) {
 184          $im1 = imagecreatetruecolor(100, 100);
 185          $im2 = imagecreatetruecolor(35, 35);
 186          $im3 = imagecreatetruecolor(512, 512);
 187          if ($image->type != IMAGETYPE_JPEG and $imagefnc === 'imagepng') {
 188              if ($t) {
 189                  // Transparent GIF hacking...
 190                  $transparentcolour = imagecolorallocate($im1 , $t['red'] , $t['green'] , $t['blue']);
 191                  imagecolortransparent($im1 , $transparentcolour);
 192                  $transparentcolour = imagecolorallocate($im2 , $t['red'] , $t['green'] , $t['blue']);
 193                  imagecolortransparent($im2 , $transparentcolour);
 194                  $transparentcolour = imagecolorallocate($im3 , $t['red'] , $t['green'] , $t['blue']);
 195                  imagecolortransparent($im3 , $transparentcolour);
 196              }
 197  
 198              imagealphablending($im1, false);
 199              $color = imagecolorallocatealpha($im1, 0, 0,  0, 127);
 200              imagefill($im1, 0, 0,  $color);
 201              imagesavealpha($im1, true);
 202  
 203              imagealphablending($im2, false);
 204              $color = imagecolorallocatealpha($im2, 0, 0,  0, 127);
 205              imagefill($im2, 0, 0,  $color);
 206              imagesavealpha($im2, true);
 207  
 208              imagealphablending($im3, false);
 209              $color = imagecolorallocatealpha($im3, 0, 0,  0, 127);
 210              imagefill($im3, 0, 0,  $color);
 211              imagesavealpha($im3, true);
 212          }
 213      } else {
 214          $im1 = imagecreate(100, 100);
 215          $im2 = imagecreate(35, 35);
 216          $im3 = imagecreate(512, 512);
 217      }
 218  
 219      $cx = $image->width / 2;
 220      $cy = $image->height / 2;
 221  
 222      if ($image->width < $image->height) {
 223          $half = floor($image->width / 2.0);
 224      } else {
 225          $half = floor($image->height / 2.0);
 226      }
 227  
 228      imagecopybicubic($im1, $im, 0, 0, $cx - $half, $cy - $half, 100, 100, $half * 2, $half * 2);
 229      imagecopybicubic($im2, $im, 0, 0, $cx - $half, $cy - $half, 35, 35, $half * 2, $half * 2);
 230      imagecopybicubic($im3, $im, 0, 0, $cx - $half, $cy - $half, 512, 512, $half * 2, $half * 2);
 231  
 232      $fs = get_file_storage();
 233  
 234      $icon = array('contextid'=>$context->id, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>'/');
 235  
 236      ob_start();
 237      if (!$imagefnc($im1, NULL, $quality, $filters)) {
 238          // keep old icons
 239          ob_end_clean();
 240          return false;
 241      }
 242      $data = ob_get_clean();
 243      imagedestroy($im1);
 244      $icon['filename'] = 'f1'.$imageext;
 245      $fs->delete_area_files($context->id, $component, $filearea, $itemid);
 246      $file1 = $fs->create_file_from_string($icon, $data);
 247  
 248      ob_start();
 249      if (!$imagefnc($im2, NULL, $quality, $filters)) {
 250          ob_end_clean();
 251          $fs->delete_area_files($context->id, $component, $filearea, $itemid);
 252          return false;
 253      }
 254      $data = ob_get_clean();
 255      imagedestroy($im2);
 256      $icon['filename'] = 'f2'.$imageext;
 257      $fs->create_file_from_string($icon, $data);
 258  
 259      ob_start();
 260      if (!$imagefnc($im3, NULL, $quality, $filters)) {
 261          ob_end_clean();
 262          $fs->delete_area_files($context->id, $component, $filearea, $itemid);
 263          return false;
 264      }
 265      $data = ob_get_clean();
 266      imagedestroy($im3);
 267      $icon['filename'] = 'f3'.$imageext;
 268      $fs->create_file_from_string($icon, $data);
 269  
 270      return $file1->get_id();
 271  }
 272  
 273  
 274  /**
 275   * Resize an image from an image path.
 276   *
 277   * This maintains the aspect ratio of the image.
 278   * This will not enlarge the image.
 279   *
 280   * @param string $filepath The full path to the original image file.
 281   * @param int|null $width The max width of the resized image, or null to only use the height.
 282   * @param int|null $height The max height of the resized image, or null to only use the width.
 283   * @param bool $forcecanvas Whether the final dimensions should be set to $width and $height.
 284   * @return string|bool False if a problem occurs, else the resized image data.
 285   */
 286  function resize_image($filepath, $width, $height, $forcecanvas = false) {
 287      if (empty($filepath)) {
 288          return false;
 289      }
 290  
 291      // Fetch the image information for this image.
 292      $imageinfo = @getimagesize($filepath);
 293      if (empty($imageinfo)) {
 294          return false;
 295      }
 296  
 297      // Create a new image from the file.
 298      $original = @imagecreatefromstring(file_get_contents($filepath));
 299  
 300      // Generate the thumbnail.
 301      return resize_image_from_image($original, $imageinfo, $width, $height, $forcecanvas);
 302  }
 303  
 304  /**
 305   * Resize an image from an image object.
 306   *
 307   * @param resource $original The image to work on.
 308   * @param array $imageinfo Contains [0] => originalwidth, [1] => originalheight.
 309   * @param int|null $width The max width of the resized image, or null to only use the height.
 310   * @param int|null $height The max height of the resized image, or null to only use the width.
 311   * @param bool $forcecanvas Whether the final dimensions should be set to $width and $height.
 312   * @return string|bool False if a problem occurs, else the resized image data.
 313   */
 314  function resize_image_from_image($original, $imageinfo, $width, $height, $forcecanvas = false) {
 315      global $CFG;
 316  
 317      if (empty($width) && empty($height) || ($forcecanvas && (empty($width) || empty($height)))) {
 318          // We need do not have the required ddimensions to work with.
 319          return false;
 320      }
 321  
 322      if (empty($imageinfo)) {
 323          return false;
 324      }
 325  
 326      $originalwidth  = $imageinfo[0];
 327      $originalheight = $imageinfo[1];
 328      if (empty($originalwidth) or empty($originalheight)) {
 329          return false;
 330      }
 331  
 332      if (function_exists('imagepng')) {
 333          $imagefnc = 'imagepng';
 334          $filters = PNG_NO_FILTER;
 335          $quality = 1;
 336      } else if (function_exists('imagejpeg')) {
 337          $imagefnc = 'imagejpeg';
 338          $filters = null;
 339          $quality = 90;
 340      } else {
 341          debugging('Neither JPEG nor PNG are supported at this server, please fix the system configuration.');
 342          return false;
 343      }
 344  
 345      if (empty($height)) {
 346          $ratio = $width / $originalwidth;
 347      } else if (empty($width)) {
 348          $ratio = $height / $originalheight;
 349      } else {
 350          $ratio = min($width / $originalwidth, $height / $originalheight);
 351      }
 352  
 353      if ($ratio < 1) {
 354          $targetwidth    = floor($originalwidth * $ratio);
 355          $targetheight   = floor($originalheight * $ratio);
 356      } else {
 357          // Do not enlarge the original file if it is smaller than the requested thumbnail size.
 358          $targetwidth    = $originalwidth;
 359          $targetheight   = $originalheight;
 360      }
 361  
 362      $canvaswidth = $targetwidth;
 363      $canvasheight = $targetheight;
 364      $dstx = 0;
 365      $dsty = 0;
 366  
 367      if ($forcecanvas) {
 368          $canvaswidth = $width;
 369          $canvasheight = $height;
 370          $dstx = floor(($width - $targetwidth) / 2);
 371          $dsty = floor(($height - $targetheight) / 2);
 372      }
 373  
 374      if (function_exists('imagecreatetruecolor')) {
 375          $newimage = imagecreatetruecolor($canvaswidth, $canvasheight);
 376          if ($imagefnc === 'imagepng') {
 377              imagealphablending($newimage, false);
 378              imagefill($newimage, 0, 0, imagecolorallocatealpha($newimage, 0, 0, 0, 127));
 379              imagesavealpha($newimage, true);
 380          }
 381      } else {
 382          $newimage = imagecreate($canvaswidth, $canvasheight);
 383      }
 384  
 385      imagecopybicubic($newimage, $original, $dstx, $dsty, 0, 0, $targetwidth, $targetheight, $originalwidth, $originalheight);
 386  
 387      // Capture the image as a string object, rather than straight to file.
 388      ob_start();
 389      if (!$imagefnc($newimage, null, $quality, $filters)) {
 390          ob_end_clean();
 391          return false;
 392      }
 393      $data = ob_get_clean();
 394      imagedestroy($original);
 395      imagedestroy($newimage);
 396  
 397      return $data;
 398  }
 399  
 400  /**
 401   * Generates a thumbnail for the given image
 402   *
 403   * If the GD library has at least version 2 and PNG support is available, the returned data
 404   * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
 405   * returns contents of a JPEG file with black background containing the thumbnail.
 406   *
 407   * @param string $filepath the full path to the original image file
 408   * @param int $width the width of the requested thumbnail
 409   * @param int $height the height of the requested thumbnail
 410   * @return string|bool false if a problem occurs, the thumbnail image data otherwise
 411   */
 412  function generate_image_thumbnail($filepath, $width, $height) {
 413      return resize_image($filepath, $width, $height, true);
 414  }
 415  
 416  /**
 417   * Generates a thumbnail for the given image string.
 418   *
 419   * If the GD library has at least version 2 and PNG support is available, the returned data
 420   * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
 421   * returns contents of a JPEG file with black background containing the thumbnail.
 422   *
 423   * @param   string $filedata The image content as a string
 424   * @param   int $width the width of the requested thumbnail
 425   * @param   int $height the height of the requested thumbnail
 426   * @return  string|bool false if a problem occurs, the thumbnail image data otherwise
 427   */
 428  function generate_image_thumbnail_from_string($filedata, $width, $height) {
 429      if (empty($filedata) or empty($width) or empty($height)) {
 430          return false;
 431      }
 432  
 433      // Fetch the image information for this image.
 434      $imageinfo = @getimagesizefromstring($filedata);
 435      if (empty($imageinfo)) {
 436          return false;
 437      }
 438  
 439      // Create a new image from the file.
 440      $original = @imagecreatefromstring($filedata);
 441  
 442      // Generate the thumbnail.
 443      return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height);
 444  }
 445  
 446  /**
 447   * Generates a thumbnail for the given image string.
 448   *
 449   * If the GD library has at least version 2 and PNG support is available, the returned data
 450   * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
 451   * returns contents of a JPEG file with black background containing the thumbnail.
 452   *
 453   * @param   resource $original The image to work on.
 454   * @param   array $imageinfo Contains [0] => originalwidth, [1] => originalheight.
 455   * @param   int $width The width of the requested thumbnail.
 456   * @param   int $height The height of the requested thumbnail.
 457   * @return  string|bool False if a problem occurs, the thumbnail image data otherwise.
 458   */
 459  function generate_image_thumbnail_from_image($original, $imageinfo, $width, $height) {
 460      return resize_image_from_image($original, $imageinfo, $width, $height, true);
 461  }