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
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\\.$#"
count: 1

View File

@ -16,6 +16,8 @@ parameters:
- '~^Return typehint of method .* has invalid type GdImage\.$~'
- '~^Property .* has unknown class GdImage as its type\.$~'
- '~^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
- '~^Parameter \#2 .* of static method PHPUnit\\Framework\\Assert\:\:assert\w+\(\) expects .*, .* given\.$~'
- '~^Method PhpOffice\\PhpSpreadsheetTests\\.*\:\:test.*\(\) has parameter \$args with no typehint specified\.$~'

View File

@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use GdImage;
use PhpOffice\PhpSpreadsheet\Exception;
class MemoryDrawing extends BaseDrawing
{
@ -21,7 +22,7 @@ class MemoryDrawing extends BaseDrawing
/**
* Image resource.
*
* @var GdImage|resource
* @var null|GdImage|resource
*/
private $imageResource;
@ -60,10 +61,71 @@ class MemoryDrawing extends BaseDrawing
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.
*
* @return GdImage|resource
* @return null|GdImage|resource
*/
public function getImageResource()
{

View File

@ -690,18 +690,21 @@ class Html extends BaseWriter
$drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
$imageData . '" alt="' . $filedesc . '" />';
} elseif ($drawing instanceof MemoryDrawing) {
ob_start(); // Let's start output buffering.
imagepng($drawing->getImageResource()); // 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.
$imageResource = $drawing->getImageResource();
if ($imageResource) {
ob_start(); // Let's start output buffering.
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.
// max-width: 100% ensures that image doesnt overflow containing cell
// width: X sets width of supplied image.
// 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;" />';
// Because of the nature of tables, width is more important than height.
// max-width: 100% ensures that image doesnt overflow containing cell
// width: X sets width of supplied image.
// 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;" />';
}
}
}