Avoid memory leak by releasing image resources

This also better support image cloning with a proper
clone of the GD resource.

#2092
This commit is contained in:
Adrien Crivelli 2021-05-16 12:38:13 +09:00
parent 50683e6068
commit 5dd00b1b1a
No known key found for this signature in database
GPG Key ID: 16D79B903B4B5874
4 changed files with 79 additions and 22 deletions

View File

@ -5920,16 +5920,6 @@ parameters:
count: 1 count: 1
path: src/PhpSpreadsheet/Worksheet/Iterator.php path: src/PhpSpreadsheet/Worksheet/Iterator.php
-
message: "#^Parameter \\#1 \\$im of function imagesx expects resource, GdImage\\|resource given\\.$#"
count: 1
path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
-
message: "#^Parameter \\#1 \\$im of function imagesy expects resource, GdImage\\|resource given\\.$#"
count: 1
path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
- -
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:\\$pageOrder has no typehint specified\\.$#" message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:\\$pageOrder has no typehint specified\\.$#"
count: 1 count: 1

View File

@ -16,6 +16,8 @@ parameters:
- '~^Return typehint of method .* has invalid type GdImage\.$~' - '~^Return typehint of method .* has invalid type GdImage\.$~'
- '~^Property .* has unknown class GdImage as its type\.$~' - '~^Property .* has unknown class GdImage as its type\.$~'
- '~^Parameter .* of method .* has invalid typehint type GdImage\.$~' - '~^Parameter .* of method .* has invalid typehint type GdImage\.$~'
- '~^Parameter \#1 \$im of function (imagedestroy|imageistruecolor|imagealphablending|imagesavealpha|imagecolortransparent|imagecolorsforindex|imagesavealpha|imagesx|imagesy) expects resource, GdImage\|resource given\.$~'
- '~^Parameter \#2 \$src_im of function imagecopy expects resource, GdImage\|resource given\.$~'
# Accept a bit anything for assert methods # Accept a bit anything for assert methods
- '~^Parameter \#2 .* of static method PHPUnit\\Framework\\Assert\:\:assert\w+\(\) expects .*, .* given\.$~' - '~^Parameter \#2 .* of static method PHPUnit\\Framework\\Assert\:\:assert\w+\(\) expects .*, .* given\.$~'
- '~^Method PhpOffice\\PhpSpreadsheetTests\\.*\:\:test.*\(\) has parameter \$args with no typehint specified\.$~' - '~^Method PhpOffice\\PhpSpreadsheetTests\\.*\:\:test.*\(\) has parameter \$args with no typehint specified\.$~'

View File

@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Worksheet; namespace PhpOffice\PhpSpreadsheet\Worksheet;
use GdImage; use GdImage;
use PhpOffice\PhpSpreadsheet\Exception;
class MemoryDrawing extends BaseDrawing class MemoryDrawing extends BaseDrawing
{ {
@ -21,7 +22,7 @@ class MemoryDrawing extends BaseDrawing
/** /**
* Image resource. * Image resource.
* *
* @var GdImage|resource * @var null|GdImage|resource
*/ */
private $imageResource; private $imageResource;
@ -60,10 +61,71 @@ class MemoryDrawing extends BaseDrawing
parent::__construct(); parent::__construct();
} }
public function __destruct()
{
if ($this->imageResource) {
imagedestroy($this->imageResource);
$this->imageResource = null;
}
}
public function __clone()
{
parent::__clone();
$this->cloneResource();
}
private function cloneResource(): void
{
if (!$this->imageResource) {
return;
}
$width = imagesx($this->imageResource);
$height = imagesy($this->imageResource);
if (imageistruecolor($this->imageResource)) {
$clone = imagecreatetruecolor($width, $height);
if (!$clone) {
throw new Exception('Could not clone image resource');
}
imagealphablending($clone, false);
imagesavealpha($clone, true);
} else {
$clone = imagecreate($width, $height);
if (!$clone) {
throw new Exception('Could not clone image resource');
}
// If the image has transparency...
$transparent = imagecolortransparent($this->imageResource);
if ($transparent >= 0) {
$rgb = imagecolorsforindex($this->imageResource, $transparent);
if ($rgb === false) {
throw new Exception('Could not get image colors');
}
imagesavealpha($clone, true);
$color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']);
if ($color === false) {
throw new Exception('Could not get image alpha color');
}
imagefill($clone, 0, 0, $color);
}
}
//Create the Clone!!
imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height);
$this->imageResource = $clone;
}
/** /**
* Get image resource. * Get image resource.
* *
* @return GdImage|resource * @return null|GdImage|resource
*/ */
public function getImageResource() public function getImageResource()
{ {

View File

@ -690,18 +690,21 @@ class Html extends BaseWriter
$drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' . $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
$imageData . '" alt="' . $filedesc . '" />'; $imageData . '" alt="' . $filedesc . '" />';
} elseif ($drawing instanceof MemoryDrawing) { } elseif ($drawing instanceof MemoryDrawing) {
ob_start(); // Let's start output buffering. $imageResource = $drawing->getImageResource();
imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't. if ($imageResource) {
$contents = ob_get_contents(); // Instead, output above is saved to $contents ob_start(); // Let's start output buffering.
ob_end_clean(); // End the output buffer. imagepng($imageResource); // This will normally output the image, but because of ob_start(), it won't.
$contents = ob_get_contents(); // Instead, output above is saved to $contents
ob_end_clean(); // End the output buffer.
$dataUri = 'data:image/jpeg;base64,' . base64_encode($contents); $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
// Because of the nature of tables, width is more important than height. // Because of the nature of tables, width is more important than height.
// max-width: 100% ensures that image doesnt overflow containing cell // max-width: 100% ensures that image doesnt overflow containing cell
// width: X sets width of supplied image. // width: X sets width of supplied image.
// As a result, images bigger than cell will be contained and images smaller will not get stretched // As a result, images bigger than cell will be contained and images smaller will not get stretched
$html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />'; $html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
}
} }
} }