Skip to content

Commit a5868a0

Browse files
authored
Merge pull request #3157 from PHPOffice/Feature_Images-from-string-or-streams
Feature - Create In-Memory Images from strings or streams
2 parents fac0e46 + c5a07da commit a5868a0

File tree

5 files changed

+234
-0
lines changed

5 files changed

+234
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12+
- Allow the creation of In-Memory Drawings from a string of binary image data, or from a stream. [PR #3157](https://github.com/PHPOffice/PhpSpreadsheet/pull/3157)
1213
- Xlsx Reader support for Pivot Tables [PR #2829](https://github.com/PHPOffice/PhpSpreadsheet/pull/2829)
1314

1415
### Changed

docs/topics/recipes.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,22 @@ $drawing->setHeight(36);
14831483
$drawing->setWorksheet($spreadsheet->getActiveSheet());
14841484
```
14851485

1486+
Note that GD images are memory-intensive.
1487+
1488+
### Creating a Drawing from string or stream data
1489+
1490+
If you want to create a drawing from a string containing the binary image data, or from an external datasource such as an S3 bucket, then you can create a new MemoryDrawing from these sources using the `fromString()` or `fromStream()` static methods.
1491+
1492+
```php
1493+
$drawing = MemoryDrawing::fromString($imageString);
1494+
```
1495+
1496+
```php
1497+
$drawing = MemoryDrawing::fromStream($imageStreamFromS3Bucket);
1498+
```
1499+
1500+
Note that this is a memory-intensive process, like all gd images; and also creates a temporary file.
1501+
14861502
## Reading Images from a worksheet
14871503

14881504
A commonly asked question is how to retrieve the images from a workbook

src/PhpSpreadsheet/Worksheet/MemoryDrawing.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use GdImage;
66
use PhpOffice\PhpSpreadsheet\Exception;
7+
use PhpOffice\PhpSpreadsheet\Shared\File;
78

89
class MemoryDrawing extends BaseDrawing
910
{
@@ -19,6 +20,12 @@ class MemoryDrawing extends BaseDrawing
1920
const MIMETYPE_GIF = 'image/gif';
2021
const MIMETYPE_JPEG = 'image/jpeg';
2122

23+
const SUPPORTED_MIME_TYPES = [
24+
self::MIMETYPE_GIF,
25+
self::MIMETYPE_JPEG,
26+
self::MIMETYPE_PNG,
27+
];
28+
2229
/**
2330
* Image resource.
2431
*
@@ -127,6 +134,120 @@ private function cloneResource(): void
127134
$this->imageResource = $clone;
128135
}
129136

137+
/**
138+
* @param resource $imageStream Stream data to be converted to a Memory Drawing
139+
*
140+
* @throws Exception
141+
*/
142+
public static function fromStream($imageStream): self
143+
{
144+
$streamValue = stream_get_contents($imageStream);
145+
if ($streamValue === false) {
146+
throw new Exception('Unable to read data from stream');
147+
}
148+
149+
return self::fromString($streamValue);
150+
}
151+
152+
/**
153+
* @param string $imageString String data to be converted to a Memory Drawing
154+
*
155+
* @throws Exception
156+
*/
157+
public static function fromString(string $imageString): self
158+
{
159+
$gdImage = @imagecreatefromstring($imageString);
160+
if ($gdImage === false) {
161+
throw new Exception('Value cannot be converted to an image');
162+
}
163+
164+
$mimeType = self::identifyMimeType($imageString);
165+
$renderingFunction = self::identifyRenderingFunction($mimeType);
166+
167+
$drawing = new self();
168+
$drawing->setImageResource($gdImage);
169+
$drawing->setRenderingFunction($renderingFunction);
170+
$drawing->setMimeType($mimeType);
171+
172+
return $drawing;
173+
}
174+
175+
private static function identifyRenderingFunction(string $mimeType): string
176+
{
177+
switch ($mimeType) {
178+
case self::MIMETYPE_PNG:
179+
return self::RENDERING_PNG;
180+
case self::MIMETYPE_JPEG:
181+
return self::RENDERING_JPEG;
182+
case self::MIMETYPE_GIF:
183+
return self::RENDERING_GIF;
184+
}
185+
186+
return self::RENDERING_DEFAULT;
187+
}
188+
189+
/**
190+
* @throws Exception
191+
*/
192+
private static function identifyMimeType(string $imageString): string
193+
{
194+
$temporaryFileName = File::temporaryFilename();
195+
file_put_contents($temporaryFileName, $imageString);
196+
197+
$mimeType = self::identifyMimeTypeUsingExif($temporaryFileName);
198+
if ($mimeType !== null) {
199+
unlink($temporaryFileName);
200+
201+
return $mimeType;
202+
}
203+
204+
$mimeType = self::identifyMimeTypeUsingGd($temporaryFileName);
205+
if ($mimeType !== null) {
206+
unlink($temporaryFileName);
207+
208+
return $mimeType;
209+
}
210+
211+
unlink($temporaryFileName);
212+
213+
return self::MIMETYPE_DEFAULT;
214+
}
215+
216+
private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string
217+
{
218+
if (function_exists('exif_imagetype')) {
219+
$imageType = @exif_imagetype($temporaryFileName);
220+
$mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null;
221+
222+
return self::supportedMimeTypes($mimeType);
223+
}
224+
225+
return null;
226+
}
227+
228+
private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string
229+
{
230+
if (function_exists('getimagesize')) {
231+
$imageSize = @getimagesize($temporaryFileName);
232+
if (is_array($imageSize)) {
233+
$mimeType = $imageSize['mime'] ?? null;
234+
235+
return self::supportedMimeTypes($mimeType);
236+
}
237+
}
238+
239+
return null;
240+
}
241+
242+
private static function supportedMimeTypes(?string $mimeType = null): ?string
243+
{
244+
if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) {
245+
return $mimeType;
246+
}
247+
248+
return null;
249+
}
250+
130251
/**
131252
* Get image resource.
132253
*
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Worksheet;
4+
5+
use GdImage;
6+
use PhpOffice\PhpSpreadsheet\Exception;
7+
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class MemoryDrawingTest extends TestCase
11+
{
12+
public function testMemoryDrawing(): void
13+
{
14+
$name = 'In-Memory image';
15+
$gdImage = @imagecreatetruecolor(120, 20);
16+
if ($gdImage === false) {
17+
self::markTestSkipped('Unable to create GD Image for MemoryDrawing');
18+
}
19+
20+
$textColor = (int) imagecolorallocate($gdImage, 255, 255, 255);
21+
imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor);
22+
23+
$drawing = new MemoryDrawing();
24+
$drawing->setName($name);
25+
$drawing->setDescription('In-Memory image 1');
26+
$drawing->setCoordinates('A1');
27+
$drawing->setImageResource($gdImage);
28+
$drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG);
29+
$drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG);
30+
31+
if (version_compare(PHP_VERSION, '8.0.0', '>=') === true) {
32+
self::assertIsObject($drawing->getImageResource());
33+
/** @phpstan-ignore-next-line */
34+
self::assertInstanceOf(GdImage::class, $drawing->getImageResource());
35+
} else {
36+
self::assertIsResource($drawing->getImageResource());
37+
}
38+
39+
self::assertSame(MemoryDrawing::MIMETYPE_DEFAULT, $drawing->getMimeType());
40+
self::assertSame(MemoryDrawing::RENDERING_DEFAULT, $drawing->getRenderingFunction());
41+
}
42+
43+
public function testMemoryDrawingFromString(): void
44+
{
45+
$imageFile = __DIR__ . '/../../data/Worksheet/officelogo.jpg';
46+
47+
$imageString = file_get_contents($imageFile);
48+
if ($imageString === false) {
49+
self::markTestSkipped('Unable to read Image file for MemoryDrawing');
50+
}
51+
$drawing = MemoryDrawing::fromString($imageString);
52+
53+
if (version_compare(PHP_VERSION, '8.0.0', '>=') === true) {
54+
self::assertIsObject($drawing->getImageResource());
55+
/** @phpstan-ignore-next-line */
56+
self::assertInstanceOf(GdImage::class, $drawing->getImageResource());
57+
} else {
58+
self::assertIsResource($drawing->getImageResource());
59+
}
60+
61+
self::assertSame(MemoryDrawing::MIMETYPE_JPEG, $drawing->getMimeType());
62+
self::assertSame(MemoryDrawing::RENDERING_JPEG, $drawing->getRenderingFunction());
63+
}
64+
65+
public function testMemoryDrawingFromInvalidString(): void
66+
{
67+
$this->expectException(Exception::class);
68+
$this->expectExceptionMessage('Value cannot be converted to an image');
69+
70+
$imageString = 'I am not an image';
71+
MemoryDrawing::fromString($imageString);
72+
}
73+
74+
public function testMemoryDrawingFromStream(): void
75+
{
76+
$imageFile = __DIR__ . '/../../data/Worksheet/officelogo.jpg';
77+
78+
$imageStream = fopen($imageFile, 'rb');
79+
if ($imageStream === false) {
80+
self::markTestSkipped('Unable to read Image file for MemoryDrawing');
81+
}
82+
$drawing = MemoryDrawing::fromStream($imageStream);
83+
fclose($imageStream);
84+
85+
if (version_compare(PHP_VERSION, '8.0.0', '>=') === true) {
86+
self::assertIsObject($drawing->getImageResource());
87+
/** @phpstan-ignore-next-line */
88+
self::assertInstanceOf(GdImage::class, $drawing->getImageResource());
89+
} else {
90+
self::assertIsResource($drawing->getImageResource());
91+
}
92+
93+
self::assertSame(MemoryDrawing::MIMETYPE_JPEG, $drawing->getMimeType());
94+
self::assertSame(MemoryDrawing::RENDERING_JPEG, $drawing->getRenderingFunction());
95+
}
96+
}

tests/data/Worksheet/officelogo.jpg

5.47 KB
Loading

0 commit comments

Comments
 (0)