diff --git a/lib/filestorage/stored_file.php b/lib/filestorage/stored_file.php index 7ecc988218d..a959baf7053 100644 --- a/lib/filestorage/stored_file.php +++ b/lib/filestorage/stored_file.php @@ -1030,4 +1030,33 @@ class stored_file { send_file_not_found(); } } + + /** + * Generates a thumbnail for this stored_file. + * + * 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 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 + */ + public function generate_image_thumbnail($width, $height) { + if (empty($width) or empty($height)) { + return false; + } + + // 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 thumbnail. + return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height); + } } diff --git a/lib/gdlib.php b/lib/gdlib.php index 7b9f46faa11..f9ac929c325 100644 --- a/lib/gdlib.php +++ b/lib/gdlib.php @@ -279,27 +279,76 @@ function process_new_icon($context, $component, $filearea, $itemid, $originalfil * @return string|bool false if a problem occurs, the thumbnail image data otherwise */ function generate_image_thumbnail($filepath, $width, $height) { - global $CFG; - if (empty($filepath) or empty($width) or empty($height)) { return false; } - $imageinfo = getimagesize($filepath); - + // Fetch the image information for this image. + $imageinfo = @getimagesize($filepath); if (empty($imageinfo)) { return false; } - $originalwidth = $imageinfo[0]; + // Create a new image from the file. + $original = @imagecreatefromstring(file_get_contents($filepath)); + + // Generate the thumbnail. + return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height); +} + +/** + * Generates a thumbnail for the given image string. + * + * 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 $filedata The image content as a string + * @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_string($filedata, $width, $height) { + if (empty($filedata) or empty($width) or empty($height)) { + return false; + } + + // Fetch the image information for this image. + $imageinfo = @getimagesizefromstring($filedata); + if (empty($imageinfo)) { + return false; + } + + // Create a new image from the file. + $original = @imagecreatefromstring($filedata); + + // Generate the thumbnail. + return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height); +} + +/** + * Generates a thumbnail for the given image string. + * + * 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 $original The image content as a string + * @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; } - $original = imagecreatefromstring(file_get_contents($filepath)); - if (function_exists('imagepng')) { $imagefnc = 'imagepng'; $filters = PNG_NO_FILTER; @@ -327,12 +376,12 @@ function generate_image_thumbnail($filepath, $width, $height) { $ratio = min($width / $originalwidth, $height / $originalheight); if ($ratio < 1) { - $targetwidth = floor($originalwidth * $ratio); - $targetheight = floor($originalheight * $ratio); + $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; + // Do not enlarge the original file if it is smaller than the requested thumbnail size. + $targetwidth = $originalwidth; + $targetheight = $originalheight; } $dstx = floor(($width - $targetwidth) / 2); @@ -340,6 +389,7 @@ function generate_image_thumbnail($filepath, $width, $height) { 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(); diff --git a/lib/tests/fixtures/gd-logo.png b/lib/tests/fixtures/gd-logo.png new file mode 100644 index 00000000000..8c3988c3905 Binary files /dev/null and b/lib/tests/fixtures/gd-logo.png differ diff --git a/lib/tests/gdlib_test.php b/lib/tests/gdlib_test.php new file mode 100644 index 00000000000..befc9101b9d --- /dev/null +++ b/lib/tests/gdlib_test.php @@ -0,0 +1,89 @@ +. + +/** + * Test gd functionality. + * + * @package core + * @category phpunit + * @copyright 2015 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * A set of tests for some of the gd functionality within Moodle. + * + * @package core + * @category phpunit + * @copyright 2015 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_gdlib_testcase extends basic_testcase { + + private $fixturepath = null; + + public function setUp() { + $this->fixturepath = __DIR__ . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR; + } + + public function test_generate_image_thumbnail() { + global $CFG; + require_once($CFG->libdir . '/gdlib.php'); + + // Test with meaningless data. + + // Now use a fixture. + $pngpath = $this->fixturepath . 'gd-logo.png'; + $pngthumb = generate_image_thumbnail($pngpath, 24, 24); + $this->assertTrue(is_string($pngthumb)); + + // And check that the generated image was of the correct proportions and mimetype. + $imageinfo = getimagesizefromstring($pngthumb); + $this->assertEquals(24, $imageinfo[0]); + $this->assertEquals(24, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + } + + public function test_generate_image_thumbnail_from_string() { + global $CFG; + require_once($CFG->libdir . '/gdlib.php'); + + // Test with meaningless data. + + // First empty values. + $this->assertFalse(generate_image_thumbnail_from_string('', 24, 24)); + $this->assertFalse(generate_image_thumbnail_from_string('invalid', 0, 24)); + $this->assertFalse(generate_image_thumbnail_from_string('invalid', 24, 0)); + + // Now an invalid string. + $this->assertFalse(generate_image_thumbnail_from_string('invalid', 24, 24)); + + // Now use a fixture. + $pngpath = $this->fixturepath . 'gd-logo.png'; + $pngdata = file_get_contents($pngpath); + $pngthumb = generate_image_thumbnail_from_string($pngdata, 24, 24); + $this->assertTrue(is_string($pngthumb)); + + // And check that the generated image was of the correct proportions and mimetype. + $imageinfo = getimagesizefromstring($pngthumb); + $this->assertEquals(24, $imageinfo[0]); + $this->assertEquals(24, $imageinfo[1]); + $this->assertEquals('image/png', $imageinfo['mime']); + } +} diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 2acf52cc3bb..dc5755c5432 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -28,6 +28,10 @@ information provided here is intended especially for developers. * Added support for custom string manager implementations via $CFG->customstringmanager directive in the config.php. See MDL-49361 for details. * Add new make_request_directory() for creation of per-request files. +* Added generate_image_thumbnail_from_string. This should be used instead of generate_image_thumbnail when the source is a string. + This prevents the need to write files to disk unnecessarily. +* Added generate_image_thumbnail to stored_file class. This should be used when generating thumbnails for stored files. + This prevents the need to write files to disk unnecessarily. === 2.8 ===