diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php index c832f619057..5009073eae7 100644 --- a/lib/filestorage/file_storage.php +++ b/lib/filestorage/file_storage.php @@ -1521,7 +1521,6 @@ class file_storage { $width = $imageinfo['width']; $height = $imageinfo['height']; - $mimetype = $imageinfo['mimetype']; if ($keepaspectratio) { if (0 >= $newwidth and 0 >= $newheight) { @@ -1553,15 +1552,53 @@ class file_storage { } } + // The original image. $img = imagecreatefromstring($file->get_content()); + + // A new true color image where we will copy our original image. + $newimg = imagecreatetruecolor($newwidth, $newheight); + + // Determine if the file supports transparency. + $hasalpha = $filerecord['mimetype'] == 'image/png' || $filerecord['mimetype'] == 'image/gif'; + + // Maintain transparency. + if ($hasalpha) { + imagealphablending($newimg, true); + + // Get the current transparent index for the original image. + $colour = imagecolortransparent($img); + if ($colour == -1) { + // Set a transparent colour index if there's none. + $colour = imagecolorallocatealpha($newimg, 255, 255, 255, 127); + // Save full alpha channel. + imagesavealpha($newimg, true); + } + imagecolortransparent($newimg, $colour); + imagefill($newimg, 0, 0, $colour); + } + + // Process the image to be output. if ($height != $newheight or $width != $newwidth) { - $newimg = imagecreatetruecolor($newwidth, $newheight); - if (!imagecopyresized($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height)) { + // Resample if the dimensions differ from the original. + if (!imagecopyresampled($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height)) { // weird throw new file_exception('storedfileproblem', 'Can not resize image'); } imagedestroy($img); $img = $newimg; + + } else if ($hasalpha) { + // Just copy to the new image with the alpha channel. + if (!imagecopy($newimg, $img, 0, 0, 0, 0, $width, $height)) { + // Weird. + throw new file_exception('storedfileproblem', 'Can not copy image'); + } + imagedestroy($img); + $img = $newimg; + + } else { + // No particular processing needed for the original image. + imagedestroy($newimg); } ob_start(); @@ -1580,6 +1617,11 @@ class file_storage { case 'image/png': $quality = (int)$quality; + + // Woah nelly! Because PNG quality is in the range 0 - 9 compared to JPEG quality, + // the latter of which can go to 100, we need to make sure that quality here is + // in a safe range or PHP WILL CRASH AND DIE. You have been warned. + $quality = $quality > 9 ? (int)(max(1.0, (float)$quality / 100.0) * 9.0) : $quality; imagepng($img, NULL, $quality, NULL); break; diff --git a/lib/filestorage/tests/file_storage_test.php b/lib/filestorage/tests/file_storage_test.php index 9c7ff1dfec7..95d60652187 100644 --- a/lib/filestorage/tests/file_storage_test.php +++ b/lib/filestorage/tests/file_storage_test.php @@ -986,6 +986,69 @@ class core_files_file_storage_testcase extends advanced_testcase { $this->assertInstanceOf('stored_file', $converted); } + public function test_convert_image_png() { + global $CFG; + + $this->resetAfterTest(false); + + $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.png'; + $syscontext = context_system::instance(); + $filerecord = array( + 'contextid' => $syscontext->id, + 'component' => 'core', + 'filearea' => 'unittest', + 'itemid' => 0, + 'filepath' => '/images/', + 'filename' => 'testimage.png', + ); + + $fs = get_file_storage(); + $original = $fs->create_file_from_pathname($filerecord, $filepath); + + // Vanilla test. + $filerecord['filename'] = 'testimage-converted-nosize.png'; + $vanilla = $fs->convert_image($filerecord, $original); + $this->assertInstanceOf('stored_file', $vanilla); + // Assert that byte 25 has the ascii value 6 for PNG-24. + $this->assertTrue(ord(substr($vanilla->get_content(), 25, 1)) == 6); + + // 10x10 resize test; also testing for a ridiculous quality setting, which + // we should if necessary scale to the 0 - 9 range. + $filerecord['filename'] = 'testimage-converted-10x10.png'; + $converted = $fs->convert_image($filerecord, $original, 10, 10, true, 100); + $this->assertInstanceOf('stored_file', $converted); + // Assert that byte 25 has the ascii value 6 for PNG-24. + $this->assertTrue(ord(substr($converted->get_content(), 25, 1)) == 6); + + // Transparency test. + $filerecord['filename'] = 'testimage-converted-102x31.png'; + $converted = $fs->convert_image($filerecord, $original, 102, 31, true, 9); + $this->assertInstanceOf('stored_file', $converted); + // Assert that byte 25 has the ascii value 6 for PNG-24. + $this->assertTrue(ord(substr($converted->get_content(), 25, 1)) == 6); + + $originalfile = imagecreatefromstring($original->get_content()); + $convertedfile = imagecreatefromstring($converted->get_content()); + $vanillafile = imagecreatefromstring($vanilla->get_content()); + + $originalcolors = imagecolorsforindex($originalfile, imagecolorat($originalfile, 0, 0)); + $convertedcolors = imagecolorsforindex($convertedfile, imagecolorat($convertedfile, 0, 0)); + $vanillacolors = imagecolorsforindex($vanillafile, imagecolorat($vanillafile, 0, 0)); + $this->assertEquals(count($originalcolors), 4); + $this->assertEquals(count($convertedcolors), 4); + $this->assertEquals(count($vanillacolors), 4); + $this->assertEquals($originalcolors['red'], $convertedcolors['red']); + $this->assertEquals($originalcolors['green'], $convertedcolors['green']); + $this->assertEquals($originalcolors['blue'], $convertedcolors['blue']); + $this->assertEquals($originalcolors['alpha'], $convertedcolors['alpha']); + $this->assertEquals($originalcolors['red'], $vanillacolors['red']); + $this->assertEquals($originalcolors['green'], $vanillacolors['green']); + $this->assertEquals($originalcolors['blue'], $vanillacolors['blue']); + $this->assertEquals($originalcolors['alpha'], $vanillacolors['alpha']); + $this->assertEquals($originalcolors['alpha'], 127); + + } + private function generate_file_record() { $syscontext = context_system::instance(); $filerecord = new stdClass(); diff --git a/lib/filestorage/tests/fixtures/testimage.png b/lib/filestorage/tests/fixtures/testimage.png new file mode 100644 index 00000000000..791ad6e6c30 Binary files /dev/null and b/lib/filestorage/tests/fixtures/testimage.png differ