From 92b9ef99174e87c65e60118aa44e83a58778da34 Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Wed, 10 Aug 2016 15:32:54 +0800 Subject: [PATCH] MDL-55445 gdlib: Support for resizing an image --- lib/filestorage/stored_file.php | 24 ++++ lib/gdlib.php | 206 ++++++++++++++++++++------------ lib/tests/gdlib_test.php | 68 +++++++++++ 3 files changed, 222 insertions(+), 76 deletions(-) diff --git a/lib/filestorage/stored_file.php b/lib/filestorage/stored_file.php index b115f965673..7d3b64f86fd 100644 --- a/lib/filestorage/stored_file.php +++ b/lib/filestorage/stored_file.php @@ -1063,4 +1063,28 @@ class stored_file { // Generate the thumbnail. return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height); } + + /** + * Generate a resized image for this stored_file. + * + * @param int|null $width The desired width, or null to only use the height. + * @param int|null $height The desired height, or null to only use the width. + * @return string|false False when a problem occurs, else the image data. + */ + public function resize_image($width, $height) { + global $CFG; + require_once($CFG->libdir . '/gdlib.php'); + + // Fetch the image information for this image. + $imageinfo = @getimagesizefromstring($this->get_content()); + if (empty($imageinfo)) { + return false; + } + + // Create a new image from the file. + $original = @imagecreatefromstring($this->get_content()); + + // Generate the resized image. + return resize_image_from_image($original, $imageinfo, $width, $height); + } } diff --git a/lib/gdlib.php b/lib/gdlib.php index 948d783ed1f..72fdb4141d9 100644 --- a/lib/gdlib.php +++ b/lib/gdlib.php @@ -270,20 +270,21 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil return $file1->get_id(); } + /** - * Generates a thumbnail for the given image + * Resize an image from an image path. * - * If the GD library has at least version 2 and PNG support is available, the returned data - * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function - * returns contents of a JPEG file with black background containing the thumbnail. + * This maintains the aspect ratio of the image. + * This will not enlarge the image. * - * @param string $filepath the full path to the original image file - * @param int $width the width of the requested thumbnail - * @param int $height the height of the requested thumbnail - * @return string|bool false if a problem occurs, the thumbnail image data otherwise + * @param string $filepath The full path to the original image file. + * @param int|null $width The max width of the resized image, or null to only use the height. + * @param int|null $height The max height of the resized image, or null to only use the width. + * @param bool $forcecanvas Whether the final dimensions should be set to $width and $height. + * @return string|bool False if a problem occurs, else the resized image data. */ -function generate_image_thumbnail($filepath, $width, $height) { - if (empty($filepath) or empty($width) or empty($height)) { +function resize_image($filepath, $width, $height, $forcecanvas = false) { + if (empty($filepath)) { return false; } @@ -297,7 +298,119 @@ function generate_image_thumbnail($filepath, $width, $height) { $original = @imagecreatefromstring(file_get_contents($filepath)); // Generate the thumbnail. - return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height); + return resize_image_from_image($original, $imageinfo, $width, $height, $forcecanvas); +} + +/** + * Resize an image from an image object. + * + * @param resource $original The image to work on. + * @param array $imageinfo Contains [0] => originalwidth, [1] => originalheight. + * @param int|null $width The max width of the resized image, or null to only use the height. + * @param int|null $height The max height of the resized image, or null to only use the width. + * @param bool $forcecanvas Whether the final dimensions should be set to $width and $height. + * @return string|bool False if a problem occurs, else the resized image data. + */ +function resize_image_from_image($original, $imageinfo, $width, $height, $forcecanvas = false) { + global $CFG; + + if (empty($width) && empty($height) || ($forcecanvas && (empty($width) || empty($height)))) { + // We need do not have the required ddimensions to work with. + return false; + } + + if (empty($imageinfo)) { + return false; + } + + $originalwidth = $imageinfo[0]; + $originalheight = $imageinfo[1]; + if (empty($originalwidth) or empty($originalheight)) { + return false; + } + + if (function_exists('imagepng')) { + $imagefnc = 'imagepng'; + $filters = PNG_NO_FILTER; + $quality = 1; + } else if (function_exists('imagejpeg')) { + $imagefnc = 'imagejpeg'; + $filters = null; + $quality = 90; + } else { + debugging('Neither JPEG nor PNG are supported at this server, please fix the system configuration.'); + return false; + } + + if (empty($height)) { + $ratio = $width / $originalwidth; + } else if (empty($width)) { + $ratio = $height / $originalheight; + } else { + $ratio = min($width / $originalwidth, $height / $originalheight); + } + + if ($ratio < 1) { + $targetwidth = floor($originalwidth * $ratio); + $targetheight = floor($originalheight * $ratio); + } else { + // Do not enlarge the original file if it is smaller than the requested thumbnail size. + $targetwidth = $originalwidth; + $targetheight = $originalheight; + } + + $canvaswidth = $targetwidth; + $canvasheight = $targetheight; + $dstx = 0; + $dsty = 0; + + if ($forcecanvas) { + $canvaswidth = $width; + $canvasheight = $height; + $dstx = floor(($width - $targetwidth) / 2); + $dsty = floor(($height - $targetheight) / 2); + } + + if (function_exists('imagecreatetruecolor')) { + $newimage = imagecreatetruecolor($canvaswidth, $canvasheight); + if ($imagefnc === 'imagepng') { + imagealphablending($newimage, false); + imagefill($newimage, 0, 0, imagecolorallocatealpha($newimage, 0, 0, 0, 127)); + imagesavealpha($newimage, true); + } + } else { + $newimage = imagecreate($canvaswidth, $canvasheight); + } + + imagecopybicubic($newimage, $original, $dstx, $dsty, 0, 0, $targetwidth, $targetheight, $originalwidth, $originalheight); + + // Capture the image as a string object, rather than straight to file. + ob_start(); + if (!$imagefnc($newimage, null, $quality, $filters)) { + ob_end_clean(); + return false; + } + $data = ob_get_clean(); + imagedestroy($original); + imagedestroy($newimage); + + return $data; +} + +/** + * Generates a thumbnail for the given image + * + * If the GD library has at least version 2 and PNG support is available, the returned data + * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function + * returns contents of a JPEG file with black background containing the thumbnail. + * + * @param string $filepath the full path to the original image file + * @param int $width the width of the requested thumbnail + * @param int $height the height of the requested thumbnail + * @return string|bool false if a problem occurs, the thumbnail image data otherwise + */ +function generate_image_thumbnail($filepath, $width, $height) { + return resize_image($filepath, $width, $height, true); } /** @@ -337,71 +450,12 @@ function generate_image_thumbnail_from_string($filedata, $width, $height) { * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function * returns contents of a JPEG file with black background containing the thumbnail. * - * @param string $original The image content as a string - * @return string|bool false if a problem occurs, the thumbnail image data otherwise + * @param resource $original The image to work on. + * @param array $imageinfo Contains [0] => originalwidth, [1] => originalheight. + * @param int $width The width of the requested thumbnail. + * @param int $height The height of the requested thumbnail. + * @return string|bool False if a problem occurs, the thumbnail image data otherwise. */ function generate_image_thumbnail_from_image($original, $imageinfo, $width, $height) { - global $CFG; - - if (empty($imageinfo)) { - return false; - } - $originalwidth = $imageinfo[0]; - $originalheight = $imageinfo[1]; - - if (empty($originalwidth) or empty($originalheight)) { - return false; - } - - if (function_exists('imagepng')) { - $imagefnc = 'imagepng'; - $filters = PNG_NO_FILTER; - $quality = 1; - } else if (function_exists('imagejpeg')) { - $imagefnc = 'imagejpeg'; - $filters = null; - $quality = 90; - } else { - debugging('Neither JPEG nor PNG are supported at this server, please fix the system configuration.'); - return false; - } - - if (function_exists('imagecreatetruecolor')) { - $thumbnail = imagecreatetruecolor($width, $height); - if ($imagefnc === 'imagepng') { - imagealphablending($thumbnail, false); - imagefill($thumbnail, 0, 0, imagecolorallocatealpha($thumbnail, 0, 0, 0, 127)); - imagesavealpha($thumbnail, true); - } - } else { - $thumbnail = imagecreate($width, $height); - } - - $ratio = min($width / $originalwidth, $height / $originalheight); - - if ($ratio < 1) { - $targetwidth = floor($originalwidth * $ratio); - $targetheight = floor($originalheight * $ratio); - } else { - // Do not enlarge the original file if it is smaller than the requested thumbnail size. - $targetwidth = $originalwidth; - $targetheight = $originalheight; - } - - $dstx = floor(($width - $targetwidth) / 2); - $dsty = floor(($height - $targetheight) / 2); - - imagecopybicubic($thumbnail, $original, $dstx, $dsty, 0, 0, $targetwidth, $targetheight, $originalwidth, $originalheight); - - // Capture the image as a string object, rather than straight to file. - ob_start(); - if (!$imagefnc($thumbnail, null, $quality, $filters)) { - ob_end_clean(); - return false; - } - $data = ob_get_clean(); - imagedestroy($original); - imagedestroy($thumbnail); - - return $data; + return resize_image_from_image($original, $imageinfo, $width, $height, true); } diff --git a/lib/tests/gdlib_test.php b/lib/tests/gdlib_test.php index befc9101b9d..8875c3c87d5 100644 --- a/lib/tests/gdlib_test.php +++ b/lib/tests/gdlib_test.php @@ -86,4 +86,72 @@ class core_gdlib_testcase extends basic_testcase { $this->assertEquals(24, $imageinfo[1]); $this->assertEquals('image/png', $imageinfo['mime']); } + + public function test_resize_image() { + global $CFG; + require_once($CFG->libdir . '/gdlib.php'); + + $pngpath = $this->fixturepath . 'gd-logo.png'; + + // Preferred height. + $newpng = resize_image($pngpath, null, 24); + $this->assertTrue(is_string($newpng)); + $imageinfo = getimagesizefromstring($newpng); + $this->assertEquals(89, $imageinfo[0]); + $this->assertEquals(24, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + + // Preferred width. + $newpng = resize_image($pngpath, 100, null); + $this->assertTrue(is_string($newpng)); + $imageinfo = getimagesizefromstring($newpng); + $this->assertEquals(100, $imageinfo[0]); + $this->assertEquals(26, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + + // Preferred width and height. + $newpng = resize_image($pngpath, 50, 50); + $this->assertTrue(is_string($newpng)); + $imageinfo = getimagesizefromstring($newpng); + $this->assertEquals(50, $imageinfo[0]); + $this->assertEquals(13, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + } + + public function test_resize_image_from_image() { + global $CFG; + require_once($CFG->libdir . '/gdlib.php'); + + $pngpath = $this->fixturepath . 'gd-logo.png'; + $origimageinfo = getimagesize($pngpath); + $imagecontent = file_get_contents($pngpath); + + // Preferred height. + $imageresource = imagecreatefromstring($imagecontent); + $newpng = resize_image_from_image($imageresource, $origimageinfo, null, 24); + $this->assertTrue(is_string($newpng)); + $imageinfo = getimagesizefromstring($newpng); + $this->assertEquals(89, $imageinfo[0]); + $this->assertEquals(24, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + + // Preferred width. + $imageresource = imagecreatefromstring($imagecontent); + $newpng = resize_image_from_image($imageresource, $origimageinfo, 100, null); + $this->assertTrue(is_string($newpng)); + $imageinfo = getimagesizefromstring($newpng); + $this->assertEquals(100, $imageinfo[0]); + $this->assertEquals(26, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + + // Preferred width and height. + $imageresource = imagecreatefromstring($imagecontent); + $newpng = resize_image_from_image($imageresource, $origimageinfo, 50, 50); + $this->assertTrue(is_string($newpng)); + $imageinfo = getimagesizefromstring($newpng); + $this->assertEquals(50, $imageinfo[0]); + $this->assertEquals(13, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + } + }